システムの設定値をRDBに格納するため、1行しか登録されない設定テーブルを用いることは良くあることだと思います。
そういったテーブルを作る場合、これまで特に根拠なくPKを設定していたのですが(これもベストではない。詳細は後述)、あるとき私は思いました。
「無意味な値をオブジェクトに持たせるのは気持ち悪い(これが本音であとは自分への言い訳)。
1行しかないのにPKもインデックス必要ないし、インデックスがあることでインデックスのロックを考慮しないといけないの良く無くね?
そうだ。もう悪しき習慣はやめてPKは排除しよう!」
そしてその設定テーブルをリリースした**「半年後の完全に無関係なリリース」**の直後、夜間バッチが落ちました…
この仕事して20年経っても、こんな恥ずかしい失敗をやらかす自戒を込めて書き起こしておくこととしました。
落ちた原因
対象のテーブルは関連するマスターテーブルと一緒に、設定変更の際は全削除・全追加する設計になっていました。
しかし、「設計変更した対象のテーブルだけ」テーブル削除がなくてINSERTだけ書いてありました。結果
- 初回のリリースは成功した
- つぎのマスター更新のときに設定テーブルが2行になった
- 2行以上あったらエラーにするコードでバッチが死んだ(これは正)
- 私ではない担当者が夜間電話対応で寝不足になった。スマヌ・・・
これまで通り、自動発番されないPKが設定されていればINSERTでエラーとなって遅くても本番リリース時には気が付けたはずなのに、そうしなかったがために初回利用時まで不具合が隠蔽される形になってしまいました。
テスト環境では頻繁にDBごと作り直しているので気が付かなかったのです。
じゃあ、やっぱりPKは設定すべきか
というと、よく考えるとそうではないですよね。
PKに異なる値をINSERTしてしまえば2行できてしまう訳で、ないよりマシですがベストでもありません。
設定テーブルのベストプラクティス
という訳で、まじめに考えつつ調べてみましたが、こちらの方式が良さそうです。
参考文献:
DB製品によって実現方法は変わりますが、つぎを満たす設計にします。
- 1行を保証するための列を用意する
- 該当列に1種類の値しか設定できない制約を付与する
- 該当列にユニーク制約を付与する
enumが利用できるデータベースでは値をひとつしか取らないenum列を作っても良いですし、SQL Serverのようにenumが無い場合はチェック制約とユニーク制約を併用しても良いでしょう。
create table CacheConfig (
Size int not null,
EffectivePeriod time(7) not null,
SingleRowConstraint bit not null default 0,
constraint CK_SingleRowConstraint check (SingleRowConstraint = 0 ),
constraint AK_SingleRowConstraint unique(SingleRowConstraint)
)
こんな感じですかね。SingleRowConstraint列が1行に限定するための列です。
defaultを設定しておくことで、実質SingleRowConstraint列は無視して扱えるので悪くないと思います。
という訳で、自戒を込めて書き残しておきます。皆さんがこんなことでトラブらないことを祈っています。