インデックスショットガン

インデックスの性質を理解して、闇雲にインデックスを作成したり、逆に全く設定しないということのないように、というもの。そりゃそうだ。
ただ、解決策に示されているのは、すでに運用中のデータベースに対するパフォーマンス改善としてのインデックスの作成であり、いわゆる物理設計としてのインデックス設計とはちがう。そして、設計段階でのインデックスの設計は意外と難しい。なぜなら、今のデータベースはコストベースのオプティマイザであるため、実際のデータの分布がわからないと、そのインデックス自体が有効か有効でないか?ということがわからない。
このため戦略としては、インデックスが使用される可能性が高い場合のみ設定するように設計するか、多少低くても設定するように設計するか、のいずれかがある。前者は運用中のパフォーマンス改善を行いつつ、必要なインデックスのみ設定していくというもの。後者は多少インデックスが無駄になっても仕方がないが、運用中のパフォーマンス改善の機会をあまり持たなくても良いようにするというもの。運用後に必要となるインデックスが途中で変わる可能性も考えられるからである。
さて、理想としては前者だろうが、後者を選択せざるを得ない機会も多い。とくに、設計担当者が最後まで残っていない場合などは、可能性のあるものをつけておく、というのはひとつの戦略であろう。定期的に遅いSQLをピックアップし、パフォーマンス改善が行える会社であれば、何も気にする必要はないのだが。

低選択性のインデックスというコラムについて

アンチパターン自体は特に議論の余地はないのだが、このコラムはすごく引っかかる。というのも、原著はとくに問題ないと思うが、監訳者の追記が引っかかる。

このコラムで述べられている選択性の定義と計算式はMySQL特有のものと思われます。

本当だろうか?そんなことはないように思うのだが。選択性(selectivity)とは以下のようなものであると本書では述べている

MySQL テーブルの総行数に対するインデックス中の個別の値の数の割合
そのほかのRDBMS テーブルの行の総数に対し、抽出条件を適用した結果の行数が占める割合

例を挙げるとすれば、Bugsテーブルが10,000件あり、その中の属性であるstatusが5種類あり、仮にそれぞれ均等分布していた場合は、

MySQL 5 / 10,000 = 0.5%
その他のRDBMS (10,000 / 5 ) / 10,000 = 20%

となり、以下の様に結論づけている。

上記コラムでは「選択性が低いと、インデックスの有効性も低下します」と述べていますが、MySQL以外のDBでは正反対であり、「選択性が高いとインデックスの有効性が低下する」ことになります。

あれ?本当かな?「選択度が高い=インデックスの有効性が高いと思っていたが」ということで、本書の参考URLをいくつか参照して調べてみよう。

Firebird

上記のような説明があるのだが、Selectivityはインデックスの統計で計量化できるとある。インデックスの統計は上記のような計算式なのだが、Selectivityが高い=インデックスの統計値が高いとは言っていない。

Postgresql

selectivityを計算しているが、これは、「テーブルの行の総数に対し、抽出条件を適用した結果の行数が占める割合」である。ヒストグラムを使っているので少しわかりにくくなっているが、言っていることは同じである。
ただ、selectivityが高いからインデックスの有効性がどうといった表現はどこにもない。

SQL Server

選択度とは、特定される行の、合計行数に対する比率です。比率が低い場合、インデックスの選択度は高くなります。

この説明の前半は本書と一致しているようだが、実は違う。つまり、この比率が低ければ、選択度が高いと言っている。比率と選択度の関係は逆だということがわかる。

Oracle

「セレクティビティが低いとデータが絞り込みやすくなるため、索引が使用されやすい」といっているので、本書の主張と同じになっている。
ただ、これはOracleコンサルタントが作った資料なので、念のためマニュアルで確認してみる。
Oracle Database パフォーマンス・チューニング・ガイド 11g Release2の「14.1.3 索引をつける列と式の選択」にはこうある。

・選択性の高い索引キーを選択します。索引の選択性は索引付けするキー値と同じ値を持つ行が表に含まれる割合です。

「14.1.4 コンポジット索引の選択」には以下のようにある。コンポジット索引とは複数属性の索引のことである。

・選択性の向上
選択性の低い複数の列または式を組み合わせることで、選択性の高いコンポジット索引を作成できる場合があります

結論

「選択性が高い=インデックスの有効性が高い」という原著の主張が正しいと思う。

フィア・オブ・ジ・アンノウン

NULLの振る舞いについて。本書ではNULLを使うことがアンチパターンでなく、一般の値としてNULLを使うことが良くないと述べている。
とくに、すべての項目にNOT NULLを使うように強制するのはよくないという。欠けている値を表現するには、何かの値を「欠けている値」として割り当て、それにしたがってアプリ側で処理する必要がある。

ただ、言い換えれば、上記のような扱いができるのであれば、NOT NULLにしたって構わないことでもある。例えば、DEFAULT値にNULLとして扱う「欠けている値」を設定しておくという規約にしておくのが良い。例えば、在職中の従業員他の退職日をNOT NULLにしたいのならば、DEFAULT値に2999/12/31を設定しておき、それを退職日未定とみなすといった設計である。

さて、この話題については有名なNOT NULL撲滅委員会が面白い。いきなり

しかし NULL を完全に排除することはできない

なのだから。
上記の結論にあるどうしようもないときだけ使う。という戦略については私も賛成する。ただ、私としては、検索の対象とならないような属性に対してはNULLを許可する、でもいいと思う。

アンビギュアスグループ

最大値や最小値が得られた値について、その最大値をもつタプル(行)の別の属性(列)を取得したいとした時に、group by句に含まれない属性をselectの属性に含めてしまうというもの。
ほとんどのRDBMSではエラーになるが、MySQLSQLiteはエラーにならないとのこと。エラーにならないんだ・・・。知らなかった。
解決策としては NOT EXISTS句を使って、最大や最小をみつけようとする相関クエリや、group by をした結果に対して、通常のテーブルとJOINするという導出テーブル、外部結合を使ってNOT EXISTSと同様の検索をするといったものが紹介されている。一番最後の解決策が複雑で面倒だが、速度が早いらしい。最近のオプティマイザなら、ほとんどおなじになると思うが・・・。

ランダムセレクション

ランダムにデータを抽出する際にorder by rand()を使うと、件数が多くなると遅いよ、というアンチパターンMySQLを使ったこともなければ、ランダムにデータを取り出すという要求もないので、知らなかったが、要は並び替えを常に行うので、遅いらしい。
で、ソートではなく、ランダム関数の戻り値を使って特定の値を引っ張ってくるか、アプリ側で頑張るかという対応がある。
本書の最後にも記述されていたが、ベンダー依存の統計関数であるtablesampleやsample関数などが使えれば、一番簡単なのだが。

プアマンズサーチエンジン

全文検索SQLでやろうとして、LIKE演算子正規表現で検索しようとすること。インデックスなどが使えないため遅い。全文検索エンジンを使いましょうというもの。
たしかにそのとおりですね。日本語の場合は単語境界をとるのが難しいのと、送り仮名など表現のブレがうまく検索できないため、全文検索エンジンは少し難しい。すくなくともここに記述されている自作のものでは動かない。別のエンジンを併用するのが良いように思う。

スパゲッティクエリ

スパゲッティな複雑なクエリを書くと、意図しない結果を出したり、デバッグも難しくなったりするというもの。
解決策としては分割統治を行うべきだということで以下の解決策を出している

ワンステップずつ

SQLを分割し、ワンステップずつ処理すべき

UNIONをつかう

分割したSQLをUNIONでつないで結果をひとつにする

CASEとSUMをつかう

条件ごとの集約なら一つでできる。

SQLSQLをつくる

複数のUPDATEを行うときには、検索結果でSQLをつくるようにして、実行する

しかし・・・

複雑なクエリを書きたくなる理由は他にあるのではないだろうか?それは、あるSQLの結果セットを使ってSQLを実行したいというものである。
分割する際の解決策として良くないのは、あるSQLの実行結果をループにして、さらにSQLを何度も実行するというやり方である。これでははじめの結果セット分のSQLが実行されるため、レスポンス的に大きな問題になる。こういう場合はむしろ一つのSQLにまとめ、DBMSにまかせたほうが良い。
また、あまりに複雑な場合は、Martin Fowlerの指摘にあるようにビューを使うのもひとつの方法である。オプティマイザがたまにおかしくなるときがあるが。

インプリシットカラム

ワイルドカードを使ったSQL文は避けろというもの。属性を加えるとエラーになったり、ワイルドカードですべての列を取得するのはパフォーマンスに影響するから、だめだよ。という。列名を明示的に指定しましょう。ということ。
確かに、使う必要のある列のみ取得すべき、というのは全く正しい。ただし、言い換えれば、すべての列を取得するという要件なら、使って良いし、むしろ使うべきだろう。
属性を加えるとエラーになると言っているが、すべての列を取得する要件なら、それは必要なはずである。
ここではタイプ数が減らせるからよいと言っているが、これはそのとおりであり、シンプルな方がよいに決まっている。また、「指定した一部の列を除いたすべての列を求めるワイルドカードはないか」と聞かれるが、そのような構文はない。が、あるととてもシンプルでわかりやすいSQLになると思う。であれば、プリコンパイラなどで作れば良い。
いずれにせよ、必要なものだけ取得するというのは守られるべき原則であり、安易にワイルドカードに頼るのはよくないというのは、そのとおりだと思う。また本書で指摘されているように、将来的に必要かもしれないから取得しておくという態度はほとんどの場合は間違えている。ただ、個人的には正規表現で列名が指定できたら・・とは思うが。

シュードキー・ニートフリーク

擬似キーの欠番を埋めるとかいうことはやめましょう。ということ。擬似キーは内部的な値なので欠番が生じても何も問題ないはずである。
ただし、本当のコードつまり、人間が管理すべきコードとしてはあまり良くないかもしれない。このため採番用のテーブルを作成して最大値を管理するなどを行うことも多い。もちろん、そうであったとしても、欠番は生じる可能性があるが、後からそれを埋めるような処置はやる必要はないように思う。そのためのコストが大きすぎる。

ディプロマティック・イミュニティ

アプリケーションの開発のルールをデータベース設計だけには適用しないということ。
データベースの設計者だけが特権的な位置にいるということがあるらしい。
よくわからないが、少なくともここで述べられている文書化やバージョン管理をしない(あるいは知らない)設計者とは仕事はしたくない。

マジックビーンズ

MVCアーキテクチャにおいて、モデルを単なるCRUDの部品として扱ってしまうと、それ以外のコントローラなどがあらゆるモデルのオブジェクトを使って、肥大化してしまい、修正が大変になるという問題。「ドメインモデル貧血症」ともいう。
フレームワークによって、MVCアーキテクチャが自動的に実現するわけではない、というのはその通りで、かつ、そのようなアーキテクチャじゃなくても、どこかのモジュールが肥大化するのは、おかしい状況に陥っているといえる。

砂の城

想定不足により深刻な障害が起きるというアンチパターン。障害が起きるまで放置するのではなく、想定をし、予防をしようというもの。とくに運用後の体制について記述されている。
ベンチマークを取っておく、テスト環境を用意する。例外処理をきっちりやっておく。RAIDだからバックアップを取らないなどというのはありえないなど。中でもポリシーを策定し、ユーザーと合意しておくということは最も重要だろう。
多分想定ができず、悲惨なことになる理由は以下のとおり。

  • 開発で手一杯でコストも期間もない
  • リスクが認識できない
  • リスクに対して、どうやっていいかわからない

また、仮に想定していたとしても、実際に起きた時に役に立たないということもある。たとえば、バックアップをとっていたが、十分ではなかったなど。
そのためにも、予めテスト環境などでリハーサルなどをやっておくべきであり、手順を明確にしておき、手順のミスによるさらなる事故を防がなくてはならない。

さいごに

最後は短くなった。なぜなら、より物理設計に近くなると異論がなくなるからである。言い換えると、論理設計は正解がない。設計者の裁量の部分が大きく、何が絶対的に正しいとは言い難い。
そもそも、アンチパターンとはなんだろうか?本書の著者によると

問題の解決を意図しながらも、しばしば他の問題を生じさせるような技法

とある。しかし、論理設計においてはたいてい他の問題を生じさせ、絶対の正解がない。また、絶対の誤りもない。よって、この定義に従うと、ほとんどの論理設計の技法アンチパターンである。本書で紹介されているアンチパターンの解決法もアンチパターンである。
一方で、このようなことも記述している。

本書「SQLアンチパターン」はSQLを使用するプログラマーが最も頻繁に犯しがちなミスを記述しています

とあるが、本当にこれらはミスなのだろうか?そうではなくて、単にメリットとデメリットが十分に把握できずに選んでしまった技法(解決法)の一覧である、という言い方をすべきではないだろうか?途中で記述したが、この本を読んだ人で最もまずい使い方は、このアンチパターンの内容は絶対にやってはいけないとする規約を作ってしまうことである。ただし、物理設計以降はそうとも言えないものも含まれている。SQLインジェクションなどをそのままにしていい理由などないため、そのとおりだろう。
途中でも記述したが、この本の著者はおそらく「リレーショナルモデルとして自然な設計をしましょう」ということが言いたかったのではないかと思う。それをアプリ側の都合による(実装上の都合による)、凝った設計をアンチパターンとよびたかったのではないだろうか?
デザインパターンの目的は再利用を目的とした設計というものに語彙を与えた、ということであった。これに対し、その反対を意味するアンチパターンとは一体どういう意味があるのだろうか?語彙を与えたという意味ではいいのかもしれないが、常に誤った設計でもないものにたいし、アンチパターンとしての語彙を与えるというのは、多くの人にミスリードを与えかねないと思う。