2013年3月20日水曜日

SQLアンチパターン

SQLアンチパターンを買ったのでそのメモ。





1.ジェイウォーク
 一つのカラムが複数の値を持つときにカンマ区切りとかで入れちゃうと、検索とかめちゃくちゃ面倒になるよね。
 例えば、ゲームのユーザが、複数のカードを持つときはどうする?
 そういう時には、交差テーブルというのを作る
 ユーザ
 ユーザ-アイテム
 アイテム
 というテーブルを作って、一人のユーザがどんなアイテムを持っているかという関係性だけを持っておく。

2.ナイーブツリー
 例えば掲示板のようなもの。
 個々の書き込みは、どれかの書き込みの返信だったりする。
 だから個々のレコードに親ID列を持つというのがありがち。
 だけど、SQLではこういう階層を持った構造が苦手。
 ファイルのディレクトリ構造はそんな複雑なしくみではない。
 単にスラッシュやバックスラッシュで区切られた「長いファイル名」があるだけ。こういうのを「経路列挙(Path Emumeration)と言う」
 
 でも、まぁツリー構造は面倒ってこと。

3.とりあえずID
 とりあえず、idって列があって、それがシーケンシャルに振られてるってことがあるけど、プライマリーキーはプライマリーキーで
 あった方がいいよね。
 さらに言えば、結合をすることも考えると、idじゃなくて、account_idとかそういう名前にした方がいいよね
 フレームワークが「id」って名前をベースに主キーと見なすってことはあるのだけれど、そんなもんテーブル定義から読み込みやがれ

4.キーレスエントリ(外部キー嫌い)
 外部キーをちゃんと使えば、データベースの構造の不一致が防げるのにあんまり使われてない。
 (これは僕もあんまり使ったことがない。構造の不一致をチェックするようなプログラムが多かったから)
 あと、意図せずレコードが消えちゃったりするのがちょっと不便。カスケード更新
 親レコードのマスタ入れ替える時に子レコードが消えちゃったら怖いし

5.EAV(KVS)
 スキーマレスとはイイながら結局、項目を定義して、項目で検索したくなったらもう死亡。
 RDBのカラムの中にJSONとか突っ込んじゃうと、むしろ編集が大変
 RDBでも
  ID(重複OK)
  属性名
  属性値
 みたいなテーブル構造だったら事実上のスキーマレス。

7.マルチカラムアトリビュート
 article_id
 tag1
 tag2
 tag3
 
 こんなのがダメ。タグで検索できないし。
 本当は従属テーブルを作る
 article_id
 tag
 いわゆる横持ちから縦持ちに変更するというやつ。
 同じ意味を持つ値は一つの列に入れたいですね。
 
8.メタデータドリブル
 売上2013
 とかいうテーブルとかカラムがあるともうだめ。
 毎年システム変えなくちゃいけない。
 「カラム数の最大は?」が気になっている時点でアウト

9.少数が有るところはDECIMAL
 Floatを使うと誤差がでるから

10.ENUMの禁止
 例えば、「県」があったら県コードの一覧からしか入れれないようにしたい。
 MySQLだとENUMがあるけど、そんなもん使わなくてもいい。
 参照整合性を持ったマスタを外部キーとして使えばいい

11.ファントムファイル
 画像はどうやってもつ?物理ファイルに持っておいて、DB上はファイル名だけ保持する?
 でもDBと整合性とれなくなることあるよね。
 BLOBを使えば全てSQLで操作できるよ。

12.闇雲インデックス
必要なところにだけインデックス。

13.恐怖のUnnown

nullが入ることで、null+何かがNULLになってしまって、その後の処理がうまくいかないってやつ。
「値が1以外」ではnullが帰ってこないとか。nullとnullを比較してもtrueにはならないし、期待通りに動かない。
案としては、列にNOTNULL制約を加えること。デフォルト値があればいい。

14.曖昧なグループ
(集計関数の技術の話なので割愛。こんなのSQL一発でやらない方がいいから)

15.ランダムセレクション
order by rand()をするとめちゃくちゃ遅くなるという話。
解決策は、主キーの最大値から最小値の間で乱数を生成して、あとはキーダイレクトで持ってくればいい。
欠番があるときには、0からレコード数の間で乱数を生成してオフセットする。

16.貧者のサーチエンジン
全文検索したかったら、LIKE演算子じゃなくて、全文検索エンジンを使いましょう。

17.スパゲッティクエリ
ややこしいクエリを一つのSQLでやろうとするなという話。
シンプルなSQLを何回かに分けた方がシンプルだよ。
どうしても、一つのSQLで表にしたかったら、UNIONで縦に並べればいいじゃない。

18.インプリシットカラム
select * しちゃうと、カラムが増えた時の影響がめちゃでかいしパフォーマンスも悪いよ。
明示的にカラム名を指定しよう。そうしておくと、カラム名が変わったときには、SQLの段階でエラーがでるからミスが発見しやすい。

19.読み取り可能パスワード
DBにはパスワードを平文で保存しちゃダメよ。ソルトをつけてパスワードハッシュを格納するようにしましょう。
パスワードを忘れたときにも、パスワードを教える仕組みにするのではなく、パスワードをリセットする仕組みにしましょう。

20.SQLインジェクション
プレースホルダを使いましょう。でもそれが使えるのって、値の部分だけで、テーブル名とかカラム名には通用しない話なので、いよいよ堅く作りたかったらストアドプロシージャで、静的なSQLにすればいい。プレースホルダでうまくいかないのなら、ユーザが渡してくる値は、「POD::quote」のようなエスケープ関数を通しなさい。
IN句のようにに「?」の数が動的な場合には、渡ってきた値の数だけ「?」を生成しなさいよと。

大原則として「ユーザには、値の入力は許可しても、コードの入力は許可しない」。

21.疑似キー潔癖症
連番っぽい主キーフィールドに欠番があるのを埋めようとしないでいい。キーの振り直しほどリスクの高いモノはない。

22.臭い物に蓋
SQLの構文エラーがあるのに、それが分かりづらいWebアプリケーションってあるよね。実行されているSQLはデバッグの際には見れるようにしよう。

23.外交特権
プログラミング言語に関しては、テストやバージョン管理の思想が行き渡っているのに、データベースに関しては割と外にやられてるよね。
コードが複数のブランチを切っているのに、データベースは一つだけってことってない?

24.魔法の豆
アクティブレコードそのままをモデルとしてむき出しにして使っていると全然データベース構造が隠蔽化できない。ロジックないからモデルをテストしても単体テストにならない。
ルールとデータをカプセル化するのがモデル。一つのモデルが複数のテーブルを操作してよい。それがドメインモデル。

25.砂の城
想定外を想定するために、負荷テストしましょう。データのサイズも大きくして。今はクラウドが使えるのだから、環境もさくっと作れるでしょう。

1 件のコメント:

  1. > 意図せずレコードが消えちゃったりするのが
    > ちょっと不便。カスケード更新
    > 親レコードのマスタ入れ替える時に子レコードが
    > 消えちゃったら怖いし

    ON UPDATEとかON DELETEで指定できる挙動は、
    CASCADEだけではないです。
    NO ACTIONやSET NULL、SET DEFAULTを指定することも
    可能ですので、要件にあった挙動を指定すれば
    いいのではないでしょうか。

    返信削除