Hatena::ブログ(Diary)

Ynishi Bussiness Logs このページをアンテナに追加 RSSフィード

2013-04-29 SQLアンチパターン2

キーレスエントリ(外部キー嫌い)

|  キーレスエントリ(外部キー嫌い)を含むブックマーク  キーレスエントリ(外部キー嫌い)のブックマークコメント

「参照整合性制約を設定しない。」という設計指針に対するアンチパターン。比較的このような設計をしているところを多く見る。

そもそも参照整合性制約を設定しないということは、ERモデルも記述する必要がない。テーブル定義はすべてExcelで定義すればよい*1。リレーショナルモデルを採用する気はなく、RDBSQLが使える単に永続的なデバイスとして使おうという狙いである。テーブルを「COBOL的なファイル」とみなした設計とも言えるだろう。本書は出来る限りリレーショナルモデルからみて自然設計をへ誘導しようとしている以上、こういう設計は批判の対象となるのは明らかである。

なぜアンチパターンなのか

完璧なコードを前提としている

参照整合性制約を設定しないということは、ミスしないということを宣言することに近い。しかし、それはあまりにも無謀だ。

ミスを調べなければならない

バグを治すことよりも、データの整合性が壊れていることを検知し、その整合性を保つように変更することはとても大変な作業である。長大な日次バッチで整合性検証をする時間などないので、たいていは顧客の指摘で発見される。最悪の場合は、もう直せないということさえ考えられる。

私のミスではありません

アプリケーションではなく、メンテナンスアドホックSQLで簡単に整合性を壊すことができる。

解決策

外部キー制約を宣言する

ポカヨケの原則を適用し、不整合が起こるような変更を変更を加える前に阻止するために、参照整合性制約を設定しようとするもの。異論はありません。

複数変更テーブルの変更をサポートする

外部キーにカスケード更新を設定することで、親側のデータを変更した際に連鎖的に変更することが可能になるということ。

とても便利な仕組みなのだが、レスポンス的な問題が起きやすい。大量データを処理するときは、極端に処理が遅くなる可能性があり、注意して使用しなければならない。

オーバーヘッドにはなりません

多少のオーバーヘッドは起きるが無視していいレベルだという主張。そもそも、整合性確認の仕組みをアプリで実装するのであれば、結局一緒だろうということは確かに言える。

ただ、私は更新時のアプリ側の実装に差が出るとは思わない。アプリ側でも結局、参照整合性制約を前提にせずにチェックを行い更新処理を行う。つまり、参照整合性制約を利用したエラーチェックはしない。これは、例えば、整合性制約のチェックをアプリでやる際に、DBが返す参照整合性制約の違反エラーから取得をしない。これをやると結構大変である。エラーの内容をシステムエラーコードから判断しつつ、エラーとなった制約から、対象の列を取ってくるなどの仕組みが必要となるが、それをやるくらいなら、普通に書く。

また、参照整合性制約は移行など大量データを更新する場合はやはり大きな差が出る。そこは後述するが、一旦整合性制約を無効化し、後で有効化するのがよい。

参照整合性制約を嫌う理由

本書では以下の様な参照整合性制約を嫌う理由が述べられている。

データの更新が参照整合性制約と衝突してしまう

複数テーブルの変更が面倒だという主張である。面倒だからといって、整合性が壊れることよりも優れているという理由にはならないと思うが。むしろテーブルを更新(ロック)するする順序が決まるほうが、デッドロックが起こりにくいという素晴らしいメリットもある。テーブルAをロックしながらテーブルBをロックしようとするトランザクションとテーブルBをロックしながらテーブルAをロックしようとするとデッドロックが起こるのであり、順番が統一されていたほうが良い。

データベースの柔軟性が極めて高いので、サポートできない

このあとのアンチパターンで詳述したい。

データベースが外部キーのために作成するインデックスが、パフォーマンスに影響すると考えている

インデックスを作成すると更新系の処理が遅くなり、スペースも使うが、まあそんなことを気にできるパフォーマンスチューニングは相当レアな状況だろう。むしろ参照整合性制約自身のレスポンスの問題がある。先述の「オーバーヘッドにはなりません」をにも記述した。

外部キーをサポートしないデータベース製品を使っている

どうしようもない。アンチパターンでもつかっていい理由になっている。逆に言えば、参照整合性制約を使う気がないのなら、積極的にそういうデータベース製品を使うべきだ。

外部キーを宣言する構文を調べなければならない

調べたらいいではないか。

その他の参照整合性制約を嫌う理由

参照整合性制約を設定することへの反対意見には、その他にこのような主張がある。この主張では、以下の様な点が指摘されているので、順番に考えていく。

データを移行する順番が必要になって煩雑

外部キーがあるテーブルよりも先に主キー側のテーブルを移行しないとデータが入らないという問題。テーブルの以降の順番を考えないといけないというもの。

これを回避するのは極めて簡単で、参照整合性制約を一時的に無効にすれば良い。入れた後に、もう一度有効化する。不整合があれば有効化する際にエラーが出て、データの移行を止めてくれる。

この文書でよくわからないのが次のところ。

この処理性能の問題は、一時的に参照整合性制約を解除する(または、チェックを行わないモードを使用する)などの方法で回避できる。しかし、一時的とはいえ制約を解除すると、誤ったデータが混入される可能性がある。別の方法でデータのチェックを行うくらいなら、RDBMSの参照整合性制約機能を利用する意味はほとんどなくなってしまう。

制約は有効化する際に整合性をチェックされる。別の方法は使わなくてもよい。

参照整合性制約を設定していると遅い

参照整合性制約が設定されているデータを移行するととても遅くなるので使うべきではないという主張。これは移行時にかぎらず、通常時でも大量にデータを更新する場合は同じである。この場合、設定をしない選択をする会社が多いようだ。しかし、大量データを更新するようなバッチ処理の場合は、まず制約を無効化して、バッチ処理終了後に有効化すれば良い。あるいはOracleの場合はチェックを遅延させることもでき、コミット時にまとめてチェックするというのもよい。

親側にないデータを外部キーの属性に仮追加するということができない

マスタデータに存在しないコードを外部キーの属性に追加したいという問題。これは先程の擬似キーで解決するのが最も易しい解決方法だろう。

アプリケーションで確実にやればよい

すごい自信ですね。としかいえません。

私の結論

やはり基本的には参照整合性制約は設定すべきであると考える。ここで語られていない理由以外では、アプリ側が整合性が保たれているという条件で検索処理を記述できる点がある。不整合があるデータを考慮した検索をする必要がない。また、参照整合性制約だけでなく、NOT NULL制約や、UNIQUE制約、CHECK制約などは積極的に使用すべきである。例えば、年月を表現するのに日付型を用いているのであれば、CHECK制約で日がかならず1日で時間分秒はすべて0であることを保証していれば、アプリ側にいらぬ前提が不必要になり、負担がかなり緩和される。

ただし、設定しなくても良い場合もあると思う。

参照整合性制約が失われる可能性があるのはどういう場合だろうか?それは基本的には更新時である。不整合とは更新時異常が発生する場合におきる。よって、更新をしないテーブル、つまりアプリケーションが追加のみを行うテーブルには設定が不要といえる。削除は外部キーのみ存在し、自身が親になるテーブルがなければ、更新時異常は起きない。こういうテーブルは一般的にマスタでもトランザクションテーブルでもない。トランザクションデータを単に集計しただけのテーブルなど検索スピードを向上させるために使用する集計テーブルや、データの過去の変更履歴を管理する履歴テーブル等である。ただし、この場合でもひとつ問題が残っている。それは保守などによりアドホックSQLを実行されると、それを防げないという点である。これは権限管理や監査ログを行う以外に防ぐ手段がない。

制約を使用するということは属性のドメインを明確にするという目的があり、リレーショナルモデルとしてもとても重要な要素の一つである。このような制約を軽視している人はやはりCOBOLなどの影響が残っているのかもしれない。

なお、契約による設計DbC)のように、テスト中は制約を設定しておくが、速度低下を気にするあまり運用後は除くという方針の会社もあるらしい。それでも構わないが、通常のオンライン処理ではそこまで影響があるとは思わない。

また、整合性制約を設定していると、ERモデルの線が多すぎて見えなくなるのがいやだということもある。これには同意したい面もあるが、こういう設計は冗長な項目を持っている非正規化テーブルを多く抱えていることに現れやすいので、注意すべきである。また、先程述べたように追加のみのテーブルには参照整合性制約は不要であると考える。

*1ERモデルをみると四角形のエンティティだけがなんの関係もなく作られていく。そんな図になんの意味があるのだろう

トラックバック - http://d.hatena.ne.jp/yuuntim/20130429/p2