本稿はデータベースソフトウェア「SQL Anywhere」およびデータベース全般に関する英語ドキュメントを翻訳する形で提供しています。図など、部分的に英語のままになっていますが、製品のSQL Anywhere自体は完全に日本語化されていますのでご安心ください。
第一の大罪は''データベースの物理設計''に関するもので、たとえばスキーマ設計の問題、テーブルの列の順序、インデックス付け、データベースページサイズの選択など、パフォーマンスに悪影響を及ぼしかねない重要な要因はいくつも考えられます。今回の記事では、SQL Anywhereのデータベース設計にまつわる具体的な問題と、ドメイン(定義域)の概念に関する内容を取り上げていきます。
ドメイン(定義域)とは
ドメインの概念は、E. F. Coddが発案した最初のリレーショナルデータモデルに含まれています。Chris Dateは、彼の重要な著書 [1, pp. 81] の中でドメインを次のように定義しています。
次に、ドメインという言葉を、すべて同じ型の、スカラー値の名前付きの集合と定めることにする。たとえば、サプライヤー番号のドメインは、サプライヤー番号として取り得るすべての番号の集合になり、出荷数量のドメインは、ゼロより大きく(たとえば)10,000より小さいすべての整数の集合になる。このように、ドメインとは値のプールであり、その中から実際の属性値が抽出される。
簡単に言うと、Coddが定義したリレーショナルモデルのドメインとは、プログラミング言語の''強い型付け''の定義のようなものです。プログラミング言語の場合と同様、ドメインを使用する1つの目的は、アプリケーション開発者が、たとえば納品伝票番号と顧客番号を比較するような間違いを犯さないようにすることです。どちらの集合の値も(おそらくは)整数ですが、納品伝票番号と顧客番号を比較しても、普通はほとんど意味がありません。
理屈はともあれ、商用のリレーショナルデータベース製品では、ドメインの機能は過去40年間ほとんど注目を集めてきませんでした。たしかに、SQL Anywhereを含むほぼすべての商用データベースシステムではDOMAIN
の定義(ユーザー定義データ型と呼ばれることもあります)がサポートされていますが、これらのDOMAIN
の実装と、リレーショナルモデルのドメインが意図するところでは大きな違いがあります。ほとんどすべての商用RDBMSシステムでは、柔軟な型指定が許されています。たとえばSQL Anywhereでは、数値列と文字列の比較を含むSQLステートメントが問題なく実行されます。Ivan Bowmanが書いたホワイトペーパー(PDF)では、整合性のない比較を評価しなければならないときにSQL Anywhereで行われる暗黙的な型変換のセマンティクスの概要が示されています(余談: 商用の製品には、それぞれ独自の暗黙的な変換規則があり、そうした比較のセマンティクスは実装依存です)。
このような柔軟性は、アプリケーション開発者からすると利点に見えるかもしれませんが、実際は罪なのです。その理由を説明しましょう。
SQLはプログラミング言語ではない
アプリケーション開発者は、SQLが「タプル関係計算」に基づくデータ特殊言語であることを肝に銘じておくことがきわめて重要です。簡単に言うと、SQLは一階述語論理に基づいており、クエリでは演算の''対象''を指定するだけで、''方法''は指定しないということです(余談: SQL/2008規格で定められたSQL言語に関係計算と関係代数の組み合わせが含まれていることはよく承知しています。INTERSECT
、EXCEPT
、およびUNION
は、集合レベルの代数演算子です。しかし、SQL SELECT
ブロックの大部分は、まだ計算ベースのままです)。
SQLのセマンティクスは述語論理に基づいているので、SQLの実装では、計算ベースのクエリを代数的演算のアクセスプラン(できればより効率的なもの)に変換することが許されています。等しい結果が得られるのであれば、演算の順序を変更するなどの代数的な書き換えも可能です。例として、次のようなネストされたクエリについて考えてみましょう。
SELECT * FROM CORPORATE_SUPPLIERS AS CS WHERE CS.SUPPLIER_ID IN ( SELECT S.SUPPLIER_ID FROM LOCAL_SUPPLIERS AS S WHERE S.ADDRESS LIKE '%Ontario%' AND S.REGION = 'Eastern Canada' )
この例では、LOCAL_SUPPLIERS
テーブルにカナダ東部のサプライヤーとして登録されていて、かつ、CORPORATE_SUPPLIERS
テーブルに存在しているサプライヤーを検索しています。
ここまでは問題ありません。このクエリはネストされたクエリとして記述してあり、セマンティクスは明快です。最初に、LOCAL_SUPPLIERS
テーブルの中からオンタリオ(Ontario)州のサプライヤーを検索し、その中間結果をフィルターとして使用して、同じキーを持つサプライヤーだけがCORPORATE_SUPPLIERS
テーブルから返されるようにしています。
多少の背景知識があれば、上述のクエリは、結合として記述した次のSQLクエリと同等であることがわかります。
SELECT DISTINCT CS.* FROM CORPORATE_SUPPLIERS AS CS, LOCAL_SUPPLIERS AS S WHERE CS.SUPPLIER_ID = S.SUPPLIER_ID AND S.ADDRESS LIKE '%Ontario%' AND S.REGION = 'Eastern Canada'
このような変換を行うことが、クエリの最適化の基本です。クエリオプティマイザーは、元のセマンティクスが維持される限り、自由にクエリを並べ替え、述語を''任意の順序で''評価できます。クエリオプティマイザーは、何よりもまず、この種の書き換えを行い、SQL要求を実行するための最も低コストの――より正確には、比較的短い時間の中で見つけ出せる最も低コストの――アクセスプランを見つけ出す際にその精巧さを発揮します。
しかし、それを台無しにするのが弱い型付けです。
もし、CORPORATE_SUPPLIERS
テーブル内のサプライヤー番号が数値であるのに、LOCAL_SUPPLIERS
テーブル内のサプライヤー番号が数値と英数字の混在状態だったらどうなるでしょうか。カナダ東部のサプライヤーが間違いなく数値のサプライヤー番号を持つことをアプリケーション開発者が知っているケースであれば、ネストされた反復のセマンティクスを使ってこのクエリが実行される場合、すべてがうまく処理されます。しかし、このクエリが結合として実行される場合、オプティマイザーがクエリの述語を評価する順序によっては、カナダ東部のサプライヤーだけがCORPORATE_SUPPLIERS
テーブルに結合されるとは限らないことがあります。また、SQL Anywhereは数値ドメインを使って数字と文字列の比較を行い、英数字の文字列は正しく変換されないので、''データ例外が発生することがあります''。
私がこの例を選んだのには理由があります。エンティティ型の階層ではこの種の問題がよく発生し、しかも''エラーが発生するかどうかはアクセスプランによる''ため、テスト中に発見することがきわめて困難だからです。真の問題――言うなれば罪――は、この2つのテーブルで、サプライヤー番号のデータ型を適切に選択していない点にあります。スキーマ内で代理キーを利用する理由はさまざまに考えられます(いくつかの問題については、このブログの別の記事で説明しています)が、クエリで期待どおりの結果が返されるようにするためには、ドメイン(または最低限でもデータ型)を標準化することが重要です。このことは、クエリオプティマイザーが適切なインデックス(存在する場合)を使って、同じ型(つまり同じドメイン)の列や値に一致する行を迅速に取得できるようにするためにも重要です。これを守らなかった場合、アプリケーション開発者は、述語の並べ替えによって不正な結果が生じることを防ぐために、IF
式などを駆使した「曲芸的SQL」を書くはめになります。しかし、IF式やそれに類する構造は、たいてい記述するのが複雑で、保守が難しく、評価にコストがかかり、検索引数になりません。
- [1] C. J. Date (1995). An Introduction to Database Systems, Sixth Edition. Addison-Wesley.