33.13. 演算子最適化に関する情報

PostgreSQLの演算子定義では、システムに演算子がどう振舞うかに関する有用なことを通知する、いくつかのオプション句を持つことができます。 これらの句により演算子を使用する問い合わせの実行速度がかなり向上しますので、これらの句は適切な時には常に提供しなければなりません。 しかし、提供する時にはそれらが正しいことを確認しなければいけません! 間違って最適化用の句を使用すると、サーバプロセスのクラッシュ、わけのわからないおかしな出力、その他有害な事が起こり得ます。 最適化用の句についてわからなければ、使用しなくても構いません。 使用された時よりも問い合わせの実行が遅くなるかもしれないというだけです。

PostgreSQLの今後のバージョンで、最適化用の句はさらに追加される可能性があります。 ここで説明するものは全て、バージョン8.2.6で有効なものです。

33.13.1. COMMUTATOR

COMMUTATOR句が与えられた場合、それは定義する演算子の交代演算子となる演算子の名前です。 取り得る全ての入力値x、yに対して、(x A y)が(y B x)と等しい時、演算子Aは演算子Bの交代演算子であると言います。 また、BはAの交代演算子となることにも注意してください。 例えば、通常、特定のデータ型用の演算子<>は互いの交代演算子になります。 また、通常、演算子+は自身が交代演算子となります。 しかし、通常、演算子-は交代演算子を持ちません。

交代可能な演算子の左オペランドの型は、その交代演算子の右オペランドの型と同一で、その逆もまた同様です。 したがって、PostgreSQLで交代演算子を検索する時に必要なものは交代演算子の名前のみになりますので、COMMUTATOR句でそれのみを与えておけば十分です。

インデックスや結合句で使用される演算子では交代演算子の情報を提供することが必須です。 これにより、問い合わせオプティマイザがその句を他の実行計画型で必要とされる形式に"ひっくり返す"ことができるためです。 例えば、tab1.x = tab2.yのようなWHERE句を持った問い合わせを考えてみます。 ここでtab1.xtab2.yはユーザ定義型で、tab2.yにはインデックスが付いていると仮定します。 オプティマイザは、この句をtab2.y = tab1.xという形にひっくり返す方法を知らない限り、インデックススキャンを生成できません。 インデックススキャン機構は演算子の左側にインデックス付けされた列があることを想定しているためです。 PostgreSQLは簡単にこの変形が有効であると前提しません=演算子の作成者がこれが有効であることを、交換演算子情報を持つ演算子であると印付けて指定しなければなりません。

自己交代演算子を定義する場合は、単にそれを指定するだけです。 交代演算子の対を定義する場合は少し複雑になります。 最初に他の未定義のものを参照するものをどう定義するのかということが問題となります。 この問題には下記の2つの解決方法があります。

33.13.2. NEGATOR

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操作が挿入されることがありますので、この現象は思ったより頻繁に起こります。

否定子の対は、上記の交代演算子のペアで説明した方法と同じ方法で定義することができます。

33.13.3. RESTRICT

RESTRICT句が与えられた場合、それは、その演算子用の制限選択評価関数を指定します (演算子名ではなく関数名であることに注意してください)。 RESTRICT句はboolean型を返す二項演算子に対してのみ有効です。 制限選択評価の目的は、現在の演算子と特定の定数値についてのWHERE

column OP constant

の条件を満たすテーブル内の行の割合を推測することです。 この形式を持ったWHERE句によって、どのくらいの行が除外されるのかを通知することで、オプティマイザの手助けをします (定数値が左項にあったら何が起こるかという疑問が生じるかもしれませんが、それはCOMMUTATORが存在する理由の1つでもあります)。

新しい制限選択評価関数の記述方法は本章の内容を超えていますが、幸いなことに、数多いユーザ定義の演算子に対し通常いくつかのシステム標準の評価関数を使用すれば事足ります。 システム標準の制限評価関数には下記のものがあります。

=用のeqsel
<>用のneqsel
<もしくは<=用のscalarltsel
>もしくは>=用のscalargtsel

こうした分類になっていることを奇妙に思うかもしれませんが、次のようなことを想定すればそれなりの意味があることが理解できるでしょう。 =は特にテーブル内の行の小さな部分を受け付けます。 <>は特に小さな部分を除きます。 <は、指定した定数がテーブル列の取る値の範囲のどの辺りにあるのかに依存する量の部分を受け付けます (これはよく発生するものです。ANALYZEによって収集される情報で、選択評価関数で使用できるように作成されます)。 <=は、同じ定数との比較において<よりも少しだけ大きな部分を受け付けます。 特に大雑把な推測以上のことを行うのは適切ではありませんので、区別する価値がないと言えるくらい似通った値です。 >>=についても同様なことが言えます。

非常に高いもしくは低い選択性を所有する演算子が、まったく等しいか等しくないかにかかわらず、eqselまたはneqselを使用しないことも往々にして可能です。 例えば、近似等号用の幾何演算子はテーブルのエントリの小部分にのみに合致すると仮定してeqselを使用します。

範囲比較のために数値スカラに変換することに多少の有意性があるデータ型を比較するために、scalarltselscalargtselを使用することも可能です。 できればsrc/backend/utils/adt/selfuncs.cconvert_to_scalar()のルーチンで理解できるところにデータ型を追加してください (今後、このルーチンはpg_typeシステムカタログの列で識別された、データ型ごとの関数で置き換えられなければなりませんが、まだ行われていません)。 これを行わなくても動きますが、オプティマイザは本来の推測機能を十分発揮することができません。

幾何演算子に対する追加選択関数areaselpositionselcontselsrc/backend/utils/adt/geo_selfuncs.cに書かれています。 本章の執筆時点では、これらは単なるスタブですが、ともかく使いたい(あるいは改良したい)こともあるでしょう。

33.13.4. JOIN

JOIN句が与えられた場合、それはその演算子用の結合選択評価関数の名前を示します (これが演算子名ではなく関数名であることに注意してください)。 JOIN句はboolean型を返す二項演算子のみ有効です。 結合選択評価の目的は、現在の演算子について、WHERE

table1.column1 OP table2.column2

を満たすテーブル内の行の割合を推測することです。 RESTRICT句の使用と同様、これはいくつかの取り得る結合手順のうち、どれが最も仕事量が少ないように考えられるのかをオプティマイザに計算させることで、大きなオプティマイザへの援助となります。

以前と同様、本章でも統合選択評価関数の作成方法は説明しません。 適用できるものがあれば、標準の評価関数の使用をお勧めします。

=用のeqjoinsel
<>用のneqjoinsel
<もしくは<=用のscalarltjoinsel
>もしくは>=用のscalargtjoinsel
2次元面積を基にした比較用のareajoinsel
2次元位置を基にした比較用のpositionjoinsel
2次元含有関係を基にした比較用のcontjoinsel

33.13.5. HASHES

HASHES句が存在する場合、それはシステムに対して、この演算子に基づいた結合にハッシュ結合方法を使っても問題がないことを伝えます。 HASHES句はboolean型を返す二項演算子にのみ有効です。 実際には、この演算子は、あるデータ型の等価性を求める演算子であった方が好ましいと言えます。

ハッシュ結合の基礎となっている仮定は、結合演算子は左項と右項の値が同じハッシュコードを持つ時にのみ真を返すことができるということです。 2つの値が異なるハッシュのバケットに置かれた場合、結合演算子の結果が必ず偽であるという仮定を、結合は暗黙的に行い、それらを比べることをしません。 したがって、等価性を表さない演算子にHASHES句を指定することはまったく意味がありません。

HASHES印を付けるためには、結合演算子はハッシュインデックスの演算子クラス内になければなりません。 演算子を作成する時には参照する演算子クラスがまだ存在しませんので、演算子の作成時にこれは強制されていません。 しかし、演算子クラスが存在しない場合に、このオペレータをハッシュ結合で使用しようとすると、実行時に失敗します。 システムは、演算子の入力データ型用のデータ型特有のハッシュ関数を検索するために、演算子クラスを必要とします。 もちろん、演算子クラスを作成する前に適切なハッシュ関数を提供しなければなりません。

ハッシュ関数を準備する時には注意が必要です。 マシンに依存することから、ハッシュ結合が適切な処理を行わずに失敗することがあるからです。 例えば、データ型が不要な部分を埋めるビットを持つ可能性がある構造体である場合、(推奨する戦略である、他の演算子と関数を作成して、不要なビットが常にゼロになることを保証しない限り、)その構造体全体を単にhash_anyに渡すことはできません。 この他の例として、IEEE浮動小数点標準を満たすマシンでは、マイナス0とプラス0は異なる値(異なるビット列)になりますが、この比較は等価と定義されます。 浮動小数点数値がマイナス0を持つ可能性があるのであれば、それがプラス0と同じハッシュコードを確実に生成するような処置が必要です。

注意: ハッシュ結合可能演算子の基となる関数はimmutableもしくはstableでなければなりません。 volatileの場合、システムはその演算子を決してハッシュ結合に使用しません。

注意: ハッシュ結合可能演算子の背後の関数が厳密(strict)な場合、その関数は完全、つまり2つの非NULL入力に対して、真または偽を返し、決してNULLを返さないものである必要があります。 この規則に従わないと、IN操作におけるハッシュ最適化は間違った結果を生成する可能性があります (特に、標準に従うとNULLが正しい答えになるところでINは偽を返すかもしれません。 もしくは、NULLという結果に対する準備をしていないといったエラーを生成するかもしれません)。

33.13.6. MERGES (SORT1, SORT2, LTCMP, GTCMP)

MERGES句が存在する場合、それはシステムに対して、この演算子に基づいた結合にマージ結合方法を使っても問題がないことを伝えます。 MERGES句はboolean型を返す二項演算子にのみ有効です。 実際には、演算子がデータ型またはデータ型のペアの等価性を表すものであることが必要です。

マージ結合は、左側のテーブル、右側のテーブルを順序よくソートし、並列にスキャンするという考えに基づいています。 したがって、両データ型には完全な順序付け機能が必要であり、結合演算子はソート順で"同じ場所"にある値の対のみを成功したものとするものである必要があります。 実際問題として、これは、結合演算子は等価性のような振舞いをしなければならないことを意味しています。 左右のデータ型が同じ(または少なくともビット単位での等価)であることが望ましいとされるハッシュ結合とは異なり、マージ結合は論理的な互換性を持つ別の2つのデータ型を取ることができます。 例えば、smallintintegerの等価性演算子はマージ結合が可能です。 両方のデータ型を論理的な互換性を保つ順番にソートする演算子のみが必要です。

マージ結合を実行するためには、マージ結合の等価性演算子に関連する4つの演算子をシステムが識別可能であることが必要です。 その演算子とは、左側オペランドのデータ型を対象とした小なり比較演算子、右側オペランドのデータ型を対象とした小なり比較演算子、2つのデータ型間での小なり比較演算子、および2つのデータ型間での大なり比較演算子の4つです (マージ結合可能な演算子が2つの異なった入力データ型を持っている場合は、これらは実際には4つの別個の演算子です。 ただし、入力型が同一の場合には、3つの小なり演算子は、全て同じ演算子になります)。 これらの演算子は、それぞれSORT1SORT2LTCMP、およびGTCMPオプションとして、個別に名前で指定することができます。 MERGESが指定された際に、これらのうちいずれかが省略されていた場合は、それぞれ<<<、および>というデフォルトの名前がシステムによって与えられます。 また、これら4つの演算子オプションのいずれかが現れた場合には、MERGESが暗黙的に想定されます。 したがって、一部のみを指定して、残りはシステムに指定させることが可能です。

この4つの比較演算子の入力データ型は、マージ結合可能な演算子の入力データ型から推定できます。 したがって、COMMUTATORの場合とまったく同様に、これらの句で必要とされるのは演算子名のみです。 演算子名に特殊なものを使用していない限り、MERGESとだけ入力すれば十分で、詳細はシステムが指定してくれます (COMMUTATORNEGATORを使用する場合と同様に、他の演算子を定義する前に等価性演算子を定義すると、システムによりダミーの演算子エントリが作成されます)。

演算子をマージ結合可能演算子とする時、制約が追加されます。 これらの制約は現在CREATE OPERATORでは検査されませんが、いずれも真でない場合に演算子を使用するとエラーが起こる可能性があります。

注意: マージ結合可能演算子の背後にある関数はimmutableもしくはstableでなければなりません。 volatileの場合、システムはその演算子を決してマージ結合に使用しようとはしません。

注意: PostgreSQL 7.3より前のバージョンでは、MERGESという略記方法は存在せず、マージ結合可能演算子を作成するためには、SORT1およびSORT2を明示的に指定する必要がありました。 また、LTCMPGTCMPオプションも以前は存在せず、これらの演算子の名前は個別に<>として組み込む必要がありました。