2015年5月2日土曜日

【本の感想】SQLアンチパターン【★★★★☆】


だいぶ前にOracleのセミナーに参加したときオススメとして紹介されてた本書。
長いこと後回しにしてたけどやっと購入して読んだ。

リレーショナルデータベースの設計や利用に関する25のアンチパターンについて具体的な例をあげて分かりやすくまとめられてる。
  • 実現したいこと
  • やってしまいがちなアンチパターンの説明
  • 見つけ方
  • 用いてもよい場合
  • 解決策
それぞれの章でこんな感じの内容が続く。

役立つ内容も多く、何より楽しく読めたのがよかった。 データベース設計を何度かしたことがある人なら「あるあるネタ」としても楽しめるんじゃないかな。
詳しい例をあげて説明されてるのでデータベース設計の経験がなくても十分理解できると思う。(正規化ぐらいは知っててほしいから応用情報取れるぐらいの人がいいかも)

データベース設計する/したい人やSQL書くことがある人なら万人にオススメ。


以下自分用メモ。

ジェイウォーク(信号無視)

一つのフィールドにカンマ区切りのリスト(文字列)を格納するやつ。

用いてもいい場合

カンマ区切りのデータが必要で、個別にアクセスする必要が全くない場合。

解決策

交差テーブル(多対多テーブル)を作成する。

感想

自分ではさすがにやったことないけど結構やっちゃう人いるよね•••。

ナイーブツリー(素朴な木)

組織図とかの階層構造を実現するために同じテーブルの別レコードを親として参照すること。
部署 [ 部署ID , 親部署ID , 部署名 ] みたいなテーブル。
思慮が浅く親への参照しかもたない素朴(ナイーブ)な解決策らしい。隣接リストとも言う。

用いてもいい場合

隣接リストがアプリケーションで求められているタスクに適している限りは有効。

解決策

代替ツリーモデルを使用する。
  • 経路列挙
  • 入れ子集合
  • 閉包テーブル

感想

隣接リストの利用はシンプルだし色んな本やサイトに書かれている気がする。
アンチパターンだと思ってなかったけど読んでたら確かにハマるなーって思った。
自分が作ったことあるのは階層構造でも浅いものだったから問題なかったんだと思う。
再帰クエリがサポートされてればそれを使って、なければ閉包テーブルを使うのがよさそう。

IDリクワイアド(とりあえずID)

何でもかんでも主キーにIDつけとけってアレ。
めっちゃやってたわー。自然キーがあってもID列作って代替キーにしてたわー(遠い目

用いてもいい場合

ORMフレームワークの規約に従う場合。自然
自然キーとなる項目があまりにも長すぎる場合。

解決策

わかりやすい名前を付け、自然キー、複合キーを活用する。

感想

自分は複合キーを主キーにすると外部キーとして使うときにキー項目すべてを持って行く必要があって、それが嫌(JOINのONとか長くなるし)だから代替キーとしてIDを付けてた。(もちろんUNIQUEKEYはつけるけど)
それに対してこの本では下記のようにかかれててなるほどなーと思った。
「このような開発者の複合キーへの拒否反応は、数学者が2次元や3次元の座標を使うことを拒否し、物体が1次元に存在するかのようにして計算を行うことに似ています。これによって幾何や三角法をはるかに単純化できることは事実ですが、対象とすべき現実世界の物体を正確に表すことはできません。」

自然キーがある場合は代替キーは無意味だと深く納得したので今後の設計に生きると思う。

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

外部キー?何それおいしいの?ってやつ。
参照整合性が鬱陶しくて外部キーつけなかったり、そもそも外部キーなんて知らない人がデータベース設計してたり。(こういう場合はExcelみたいな使い方されてるけど)
結局その整合性はアプリケーション側で保たないといけなくなってどうせ破綻するよねって話。

用いてもいい場合

外部キーをサポートしていないDBMSを使う場合は仕方が無い。

解決策

外部キー制約を宣言する。

感想

オーバーヘッドがーってどこかでみた気がするけどこの本で一蹴されてる。
アプリケーション側でチェックのために余分なSelectしたりする事考えたら当たり前やなぁ。

EAV(エンティティ・アトリビュート・バリュー)

Key-Value型のテーブルを用意し、属性を「行」に格納すること。

用いてもいい場合

正当化する理由は簡単には見つからない。

解決策

サブタイプのモデリングを行う。しっかり考えれば別の解決法はいくつかある。
具体的には下記方法がある。
  • シングルテーブル継承
  • 具象テーブル継承
  • クラステーブル継承
  • 半構造化データ

感想

これは一度やっちゃったことあるので耳が痛い話だった。一生こんな設計はしまい。

ポリモーフィック関連

複数の親テーブルを参照するために二重目的の外部キーを使用すること。
ある列にタイプを持たせ、そのタイプに応じて外部キー列の意味合いを変えるみたいな。
Comments ( comment_id , issue_type , issue_id , ... )とか

この例だとissue_typeによってissue_idが参照するテーブルを切り替える。
複数の無差別な関連を持つのでポリモーフィック関連と呼ばれるらしい。
これをやってしまうと参照整合性(外部キー)制約をつけられない。

用いてもいい場合

なし

解決策

交差テーブルを作成する。共通の親テーブルの作成。

感想

これもやったことあるかな。悩むけど要件は満たせるしやっちゃうのよね。
キーレスエントリのアンチパターンを防ぐためにもちゃんと交差テーブル用意しなきゃダメだね。

マルチカラムアトリビュート(複数列属性)

複数の列を定義する。

Bugs ( bug_id , description , tag1 , tag2 ,tag3 )

個数が制限され、検索しづらくなり、タグの追加・削除が複雑になり、一意性の保証ができなくなり、いいことは全くない。

用いてもいい場合

同じテーブルを参照するが、意味合いが違う場合のみ利用可能。 Bugs ( bug_id , description , 報告作業者ID, 修正作業者ID , 確認作業者ID ) みたいな。

これはそもそも各列の意味が違うから根本的に違うと思うけど・・・

解決策

従属テーブルを作成する。 BugTags ( bug_id , tag )

感想

人の作ったMSAccessとか開いてみるとこうなってること多いよね。Excel風。

メタデータトリブル(メタデータ大増殖)

スケーラビリティを高める目的で、テーブルや列をコピーすること。
2008年バグテーブル、2009年バグテーブル、2010年バグテーブル、、、みたいな。

用いてもいい場合

過去データを最新のデータから分離するようなアーカイブ目的の場合。

解決策

パーティショニングと正規化を行う。
水平パーティショニング(またはシャーディング)はDBMSが対応していれば可能。
垂直パーティショニングはBLOBやText等、サイズが大きい列を別テーブルに逃がしたりすること。

または従属テーブルの導入を行う。

感想

これもやりがち。水平パーティショニングは使えるRDBMSが限られてるし膨大な件数を扱う場合はどうするのがいいんだろうなーって思いながら読んでた。

ラウンディングエラー(丸め誤差)

floatやdouble使うと誤差でるよねって話。

用いてもいい場合

intやnumberより大きな値を扱わなければいけない場合。科学技術計算とか。

解決策

numericデータ型を使用する。

感想

これはデータベースに限った話じゃないね。

サーティワンフレーバー(31のフレーバー)

列に入る値を列定義で限定する。
Check制約やドメイン、ユーザ定義型など。
入れたい値の種類が増えるときかなり大変なことに。入れられる値を簡単には確認できないのも面倒だし。

用いてもいい場合

有効値の変更が不要だと断言できる場合。(左/右、有効/無効、オン/オフ等)

解決策

参照テーブルを用意し、限定する値をデータで指定する。

感想

bool型がないRDBMSだとintフィールドを定義してCheck制約で0,1に限定したりするなぁ。bool以外で値を限定したい場合は参照テーブル用意してるし大丈夫かな。

ファントムファイル(幻のファイル)

画像をはじめとする大容量メディアファイルは物理ファイルとして保存することが必須と思い込む。
(物理ファイルとして保存することがアンチパターンなのではない。色々な方法を検討せずに物理ファイルとして保存すべき!と思い込むことがアンチパターン)

用いてもいい場合

大容量ファイルを外部ファイルとして保存することはいくつも正当な理由がある。
  • データベースの容量を減らせる
  • データベースのバックアップ時間、容量を減らせる
  • 外部ファイルならプレビューや編集が用意

解決策

必要に応じてBLOB型を採用する。
物理ファイルもBLOBも一長一短あるので目的に合わせて選択する。

感想

あまりにも収集する画像が膨大すぎてDBストレージに保存するって選択肢が選べないことがあったなぁ。

インデックスショットガン(闇雲インデックス)

インデックスを使わない。もしくは全フィールドにインデックスを貼ること。
インデックスを使うと遅くなると思っていたり、よくわからないから闇雲に貼ったり。

用いてもいい場合

汎用的なデータベースを設計する場合でどのようなクエリを最適化しなければいけないか不明な場合。(この場合、不要なインデックスを削除する必要が出てくる)

解決策

「MENTOR」の原則に基づいてインデックス管理を行う。
  • Measure(測定)
  • Explain(解析)
  • Nominate(指名)
  • Test(テスト)
  • Optimize(最適化)
  • Rebuild(再構築)

感想

MENTORは聞いたことあったけどちゃんと読んだのは初めてだった。
インデックスの調整が必要になったら参考にしてみよう。

フィア・オブ・ジ・アンノウン(恐怖のunknown)

NULLと一般値の混同。
nullはfalseだろうとか、null+空文字は空文字だろうとか、そういう思い込み。

用いてもいい場合

NULLは適切に扱う。

解決策

NULLを一意な値と認識して扱う。
ISNULLやCOALESCEをうまく使う。
必要ならNOTNULLを正しく定義する。

感想

null怖いって人ごく稀にいるよね。
まぁこれは慣れて覚えるしかない。

アンビギュアスグループ(あいまいなグループ)

一度のgroup byでmax(date)とmax(id)とかを取ってくること。
max(date)となった行とmax(id)となった行は一致するとは限らない。

用いてもいい場合

曖昧な動作が問題にならない場合。

解決策

曖昧でない列を使用する。
  • 関数従属性のある列にのみクエリを実行する。
  • 相関サブクエリを使用する
  • 導出テーブルを使用する
  • JOINを使用する
  • 他の列に対しても集約関数を使用する
  • グループごとにすべての値を連結する

感想

誰もが経験済みなんじゃないかな。
自分は導出テーブルを使うことがほとんど。

ランダムセレクション

ランダムに行を取得したい時、全件ソートをしてしまう。
select * from bugs order by rand() limit 1; みたいな。

用いてもいい場合

データセットが十分に少ない場合。

解決策

特定の順番に依存しない。
  • 1と最大値の間のランダムなキー値を選択する(キー値が欠番な場合がある)
  • 欠番の穴の後にあるキー値を選択する(結果が偏る)
  • すべてのキー値を受け取り、ランダムに1つを選択する(アプリケーション側で処理)
  • オフセットを用いてランダムに行を選択する(Limit,rownum等を利用)
  • ベンダー依存の方法(SQLServerのTABLESAMPLE等)

感想

ランダムな行を返したいってあまり必要になったことないなぁ。
LIMIT使うのが現実的か。

プアマンズ・サーチエンジン(貧者のサーチエンジン)

Like '%hoge%' や正規表現で全文検索を行うこと。
インデックスが活用できずフルスキャンとなる。

用いてもいい場合

めったに使用しないクエリ。

解決策

適切なツールを利用する。
  • ベンダー拡張
  • テキストインデックス
  • RDBSから独立した全文検索エンジンの利用

感想

検索機能とかを実装するためには必要になるんだろうなぁ。
ベンダー拡張が使えるならそれが手軽かなと思った。

スパゲッティクエリ

SQLクエリ数は少ないほうがいいと思い込んでいる。
複雑な問題をワンステップで解決しようとし、
3つのシンプルなクエリより複雑な1つのクエリを書いてしまうこと。

用いてもいい場合

単一のクエリをデータソースにひも付けて表示する場合など。

解決策

分けて書く。
全く同じ結果を求めるなら単純なほうが優れている。

感想

うーんよく見かける。プログラミングと同じ、シンプルに考えよう。

インプリシットカラム(暗黙の列)

タイプ数を減らす目的で*を多用する。

用いてもいい場合

試しにSQLを実行する場合など。

解決策

列名を明示的に指定する。

感想

これは以前から徹底してるので問題なし。

リーダブルパスワード(読み取り可能パスワード)

データベースに読み取り可能な形でパスワードを保存する。
ユーザが入力したパスワードを読み取り可能な形でSQLクエリを作る。

用いてもいい場合

作成するアプリケーションが外部アプリケーションのクライアントとなる場合のみ。

解決策

ソルトをつけてパスワードをハッシュ化する

感想

パスワードそのままDBに突っ込むのは問題外だしハッシュ化したものを保存するけど、ソルトの扱いに毎回悩む。どこにソルトを置いておくのかとか。

SQLインジェクション

動的なSQLを記述し、未検証な入力をコードとして実行する。

用いてもいい場合

なし

解決策

誰も信用しない。
  • 変数のパラメータ化
  • プリペアドステートメント
  • コードレビュー

感想

変数のパラメータ化は当然だけどどこまでやっても怖いよね。

シュードキー・ニートフリーク(擬似キー潔癖症)

擬似キー列(連番とか)の欠番を気にして埋める。

用いてもいい場合

なし

解決策

欠番は埋めない。
ロールバックや行削除によって空いた欠番を埋めてはならない。

感想

当たり前。
値を埋めろという上司を説得する方法まで載ってて面白かった。

シー・ノー・エビル(臭いものに蓋)

戻り値を 確認せず、肝心な部分を見逃す。
connection生成時、SQL実行時等、適切なタイミングでエラーチェックを行えばすぐに問題は解決できる。それらを怠って「なぜか動かない」って騒ぐのがアンチパターン。

用いてもいい場合

エラーに対して何もする必要がない場合。(アプリケーション終了時など)

解決策

エラーや戻り値を適切にチェックし、エラーから優雅に解決する。

感想

ちゃんとやるのは大事だけど例外対応って結構めんどうだよね。

ディプロマティック・イミュニティ(外交特権)

システム開発のベストプラクティス(ソース管理を行い、テストを実行し、ドキュメントを整備するなど)を行うに当たって、SQL(特にDDLなど)を特別扱いして別扱いにすること。DBAが専任で存在する場合などに別扱いされやすい。

用いてもいい場合

その場限りのコードの場合。

解決策

包括的に品質問題に取り組む。
品質保証(Quality Assurance)のために下記を行う。
  • プロジェクト要件の明確な定義・文書化
  • 要件に対する解決策の設計・構築
  • 解決策が要件を満たしていることの確認・テスト

感想

たしかにデータベース管理者ってプログラマからすると特殊な位置に見えるよね。仲良く仕事しましょ。

マジックビーンズ(魔法の豆)

MVCのM(モデル)を単純化する目的で、モデルをアクティブレコードそのものとしてしまう。

用いてもいい場合

プロトタイプ作成時

解決策

モデルがアクティブレコードを「持つ」ようにする。
ドメインモデルの使用。

感想

なんとなーく言いたいことはわかるけどそもそもMVCをちゃんと理解できてないのでよくわからなかった。

砂の城

想定不足。性能問題や障害が起きた時どう対処するかといったポリシーが策定されていない。

用いてもいい場合

コストとの兼ね合いでどこまで行うか検討する。

解決策

どのようなトラブルが起こりうるかを可能な限り想定しておく。
トラブルは「当然起きる日常的なもの」と認識する。

感想

これもデータベースに限った話ではなくシステム運用全般に関わる話やね。



ええ本でした。
Related Posts Plugin for WordPress, Blogger...