著者: Tom Lane 氏
PostgreSQLの演算子定義には、システムに演算子がどうふるまうかに関する有効な事を伝える、幾つかのオプション句を持つ事ができます。これらの句はその演算子を使用する問い合わせ実行の際に、これらの句の情報により、かなりの速度向上がなされるので、適当な時には常に適応できる状態にしておかなければなりません。しかし、適応する際、それらが正しい事を確認しなければいけません。最適化用の句を間違って使用すると、バックエンドのクラッシュ、不思議な間違った出力、その他有害な事が起こります。最適化用の句について解らなければ、使用しなくても構いません。使用された時よりも問い合わせの実行が遅くなるかもしれないというだけです。
最適化用の句は、PostgreSQLの今後のバージョンで更に追加される可能性があります。ここで記述したものは全て、バージョン 7.2.3 で有効なものです。
COMMUTATOR句が与えられた場合、それはある演算子に定義された演算子の交代演算子であると名付けます。取り得る全ての入力x 、yに対して、(x A y)が(y B x)と等しい時、演算子Aは演算子Bの交代演算子であるといいます。また、BはAの交代演算子となることにも注意して下さい。例えば、特定の型用の演算子 < と > は通常、互いの交代演算子になります。演算子 + は通常自身が交代演算子となります。しかし、演算子 - は通常交代演算子を持ちません。
交代された演算子の左オペランドの型は、その交代演算子の右オペランドの型と同一で、その逆も又同様です。したがって、PostgreSQLで演算子を検索する時に必要なものは交代演算子の名前のみであり、 COMMUTATOR句ではそれのみが与えられていればよいです。
交代演算子である演算子を定義する場合は、単にそれを指定するだけです。交代演算子のペアを定義する場合は少し複雑になります。最初に他の未定義のものを参照するものをどう定義するのかということが問題となります。この問題には下記の2つの解決方法があります。
1つ目の方法は、最初の演算子を定義する際にCOMMUTATOR句を省略し、2番目の演算子の定義では、COMMUTATOR句に最初の演算子を与えるという方法です。 PostgreSQLは交代演算子がペアになっていることが解っていますので、2番目の定義を見た時に、自動的に最初の定義に戻ってその未定義になっているCOMMUTATOR句を設定します。
もう1つの方法は、両方の定義に COMMUTATOR 句を含めるというもっと素直な方法です。 PostgreSQLは最初の定義を処理する際に、 COMMUTATOR が存在しない演算子を参照している事が解ると、システムはその演算子用の仮のエントリをシステムカタログに作成します。この仮エントリには、PostgreSQLがこの時点で推定できる演算子名、左オペランドの型、右オペランドの型、及び結果の型についてのみの有効なデータが入ります。最初の演算子のカタログエントリはこの仮エントリに結び付きます。この後、2番目の演算子が定義されたら、システムはその仮エントリに追加情報を更新します。更新される前に仮演算子を使用すると、エラーメッセージが出力されます。(注意: バージョン6.5より前のPostgreSQL ではこの方法は信頼性がありませんでしたが、今ではこの方法が推奨されています。)
NEGATOR句が定義された場合、それはある演算子に定義された演算子の否定子であるとします。入力値、x とyの取り得る全ての値に対して両方の演算子がブール値を返し、(x A y)がNOT (x B y)と等しい場合、演算子Aは演算子Bの否定子であるといいます。また、BはAの否定子でもあることに注意して下さい。例えば、ほとんどの型では< と>=は否定子のペアとなります。演算子が自身の否定子になることは決してありません。
交代演算子とは異なり、単項演算子のペアは互いの否定子になり得ます。それはつまり、xの取り得るすべての値に対して、(A x)が NOT (B x)に等しいこと、また、右単項演算子の場合も同様の等式がなりたつことを意味します。
演算子の否定子は、COMMUTATOR 句と同様に、その演算子の左オペランド、右オペランドの型と同じものをとらなければなりません。演算子の名前のみは NEGATOR 句で指定されたものでなければいけません。
NOT (x = y) といった式をx <> yといった形に単純化させることが可能なので、否定子を与える事は問い合わせオブティマイザにとって非常に役に立ちます。他の再配置の結果としてNOTが挿入されることがありますので、この現象は想像以上に多く起こります。
否定子のペアは、上記の交代演算子のペアで説明した方法と同じ方法で定義することができます。
RESTRICT 句が与えられた場合、それは、その演算子用の制限選択評価関数を指定します。(演算子名ではなく関数名であることに注意して下さい。)RESTRICT 句はboolean型を返す二項演算子に対してのみ有効です。制限選択評価の背景には、指定した演算子と特定の定数に対して、下記のWHERE 句の条件を満たすものはテーブルの中でどのくらいの割合の行が存在するかを推測することです。
column OP constant
この形式を持ったWHERE句を使って、どのくらいの行が除外されるのかを通知することで、オブティマイザの手助けをします。(定数値が左項にあったら何が起こるかという疑問が生じるかも知れませんが、それは COMMUTATOR が存在する理由の1つでもあります。)
新しい制限選択評価関数の記述方法はこの章の内容を越えていますが、幸いな事に、大抵の場合システムが持つ標準的な評価関数を使って、多くの自作演算子用が作成できます。標準的な制限評価関数には下記のものがあります。
eqsel for = |
neqsel for <> |
scalarltsel for < or <= |
scalargtsel for > or >= |
非常に高い/低い選択性をもつ演算子に、全く等しい場合/等しくない場合で、 eqselかneqselを使用しないでおく事も可能です。 例えば、ほぼ等しい幾何演算子ではテーブルのエントリの小部分にのみに合うものと仮定して eqsel を使用します。
範囲比較のために数スカラーに変換された際に、敏感になる型を比較するために、scalarltselとscalargtselを使用することも可能です。可能であるならばsrc/backend/utils/adt/selfuncs.cにある、 convert_to_scalar()のルーチンで理解できるところにデータ型を追加して下さい。(今後、このルーチンはpg_typeシステムカタログの列で識別された、データ型毎の関数で置き換えられなければなりませんが、まだ行われていません。)これを行わなくても動きますが、オブティマイザの推測機能はその機能を発揮できません。
幾何演算子に対しての追加選択関数(areasel、positionsel、contsel)は src/backend/utils/adt/geo_selfuncs.cに書かれています。このドキュメントを書いている時点では、これらは単なるスタブですが、ともかく使いたい(あるいは改良したい)こともあるでしょう。
JOIN 句が与えられた場合、それはその演算子用の結合選択評価関数の名前を示します。(これが演算子名ではなく関数名であることに注意して下さい。) JOIN 句はboolean型を返す二項演算子のみ有効です。結合選択評価関数の背景には、対象とする現在の演算子で、下記の形式のWHERE句条件をみたす行は二つのテーブルの間でどのくらいの割合で存在するのかを推測する事があげられます。
table1.column1 OP table2.column2
RESTRICT 句の使用と同様、これは、いくつかの取り得る結合シーケンスのうちどれが最も仕事量が少ないように考えられるのかをオブティマイザに計算させることで大きなオブティマイザへの援助となります。
HASHES 句が存在する場合、それはシステムに対して、この演算子に基づいた結合にハッシュ結合方法を使っても問題が無い事を伝えます。HASHES 句はboolean型を返す二項演算子にのみ有効です。実際には、あるデータ型の等価性を求める演算子であった方が良いです。
ハッシュ結合の基礎となっている仮定は、結合演算子は左項と右項の値が同じハッシュコードを持つ時にのみ真を返すことができるということです。2つの値が異なるハッシュの入れ物に置かれた場合、結合演算子の結果が必ず偽であるという仮定を、結合は暗黙的に行ない、それらを比べる事をしません。したがって、等価性を表さない演算子にHASHES句を指定することは全く意味がありません。
実際は、論理的な等価性はあまり十分ではなく、演算子は純粋にビット単位の等価性を表すものの方が好ましいです。なぜならば、ハッシュ関数は、ビットの意味は関係なく、メモリ内の値表現を使って計算されるからです。例えば、時間間隔の等価性はビット単位での等価性ではなく、間隔の等価性演算子は、二つの時間間隔がその終了時刻が異なっていた場合でも、期間が同一であれば、その時間間隔は等価であるとみなします。これは、間隔フィールドとの間で = を使った結合は、ハッシュ結合を実装した場合とそれ以外で実装した場合とで、異なる結果をもたらすことを意味しています。それは、合うべきペアの多くの部分は異なる値にハッシュされ、ハッシュ結合時に比較されなくなるために起こります。しかし、オブティマイザが他種類の結合を使用する事を選んだ場合、等価性演算子が同一で あるとした全てのペアが見つかります。このような矛盾は好ましくありませんので、間隔の等価性をハッシュ可能とはしません。
マシンに依存することから、ハッシュ結合が適切な処理を行なわずに失敗することがあります。例えば、データ型が不要な部分を埋めたビットを持つ可能性がある構造である場合、その等価性演算子にHASHES 句をつけることは安全ではありません。(他の演算子を不要なビットが常に0になるように作成している場合は状況が変わる場合があります。)この他の例として、浮動小数点データ型でハッシュ結合を使用するには安全ではない場合があります。 IEEE 浮動小数点標準をみたすマシンではマイナス0とプラス0は異なる値(異なるビット列)となりますが、等価であるものと定義されます。したがって、浮動小数点データ型の等価性演算子にHASHES 句を付けると、マイナス0とプラス0はハッシュ結合では多分一致されませんが、他の結合処理では一致するものとされます。
つまり、memcmp() で実装された(できた)等価性演算子にのみにHASHES 句を使用するべきです。
SORT 句がある場合、それはシステムに対して指定演算子に基づいた結合にマージ結合方式を使う事ができることを伝えます。どちらか一方がそうであるならば、両方が指定されなければならなく、指定演算子はあるデータ型のペアの等価性演算子でなければいけません。また、SORT1句とSORT2句はそれぞれ左側、右側用の順序付演算子( "<" 演算子)の名前を示します。
マージ結合は、テーブルの左側、右側を順序良くソートし、並列にスキャンするという考えに基づいています。したがって、両データ型は十分に順序付けされている必要があり、結合演算子はソート順で "同じ場所" にある値のペアをのみを成功したものとするものである必要があります。実際問題として、これは、結合演算子は等価性のような振舞いをしなければならないことを意味しています。左右のデータ型が同じ(または少なくともビット単位での等価)であることが望ましいとされるハッシュ結合とは異なり、マージ結合は論理的な互換性を持つ別の2つのデータ型をとることができます。例えば、int2対int4の等価性演算子はマージ結合が可能です。両方のデータ型を論理的な互換性を保つ順番にソートする演算子のみが必要です。
マージソート演算子を指定する時は、対象とする演算子と参照された両演算子はboolean型を返さなければいけません。SORT1演算子は、対象とする演算子の左オペランドの型と同じデータ型を入力として2つ持たなければいけません。SORT2 演算子は、対象とする演算子の右オペランドの型と同じデータ型を入力として2つ持たなければいけません。(COMMUTATORとNEGATORを使う時と同じように、演算子名は演算子の指定に十分なものです。他を定義する前に、等価性演算子を定義すると、システムは仮演算子エントリを作成する事ができます。)
実際には、=演算子用の SORT 句だけを記述し、2つの参照される演算子を常に < という名前にしておくべきです。他の名前の演算子を使ってマージ結合を使用すると、絶望的な混乱を引き起こします。その理由は後に説明します。
マージ結合を行なう演算子には追加的な制約があります。この制約は今のところ CREATE OPERATOR で点検されませんが、これが真でなければ、マージ結合は実行時に失敗する可能性があります。
マージ結合が可能な等価性演算子は交代演算子(2つのデータ型が同じならば、演算子自身、もしくはこれに関連した等価性演算子)を持つ必要があります。
左右のオペランドとも同じ入力データ型で、かつマージ結合可能な演算子を持つ順序付演算子 < と > が必要です。この演算子は<と>という名前である必要があり、これらを明示的に指定する方法はないので、この点については選択の余地がありません。左右のデータ型が異なる場合、この演算子は互いの SORT 演算子と異なるものであることに注意して下さい。しかし、SORT 演算子を使った場合の順序と互換性を持たせなければ、マージ結合は失敗します。