PostgreSQLの演算子定義では、システムに演算子がどう振舞うかに関する有用なことを通知する、いくつかのオプション句を持つことができます。 これらの句により演算子を使用する問い合わせの実行速度がかなり向上しますので、これらの句は適切な時には常に提供しなければなりません。 しかし、提供する時にはそれらが正しいことを確認しなければいけません! 間違って最適化用の句を使用すると、問い合わせの低速化、わけのわからないおかしな出力、その他有害な事が起こり得ます。 最適化用の句についてわからなければ、使用しなくても構いません。 使用された時よりも問い合わせの実行が遅くなるかもしれないというだけです。
PostgreSQLの今後のバージョンで、最適化用の句はさらに追加される可能性があります。 ここで説明するものはすべて、バージョン12.4で有効なものです。
演算子の基となる関数にプランナサポート関数を結び付けて、システムに演算子の振舞いを通知する別の方法を提供することも可能です。 より詳細な情報については37.11を参照してください。
COMMUTATOR
COMMUTATOR
句が与えられた場合、それは定義する演算子の交代演算子となる演算子の名前です。
取り得る全ての入力値x、yに対して、(x A y)が(y B x)と等しい時、演算子Aは演算子Bの交代演算子であると言います。
また、BはAの交代演算子となることにも注意してください。
例えば、通常、特定のデータ型用の演算子<
と>
は互いの交代演算子になります。
また、通常、演算子+
は自身が交代演算子となります。
しかし、通常、演算子-
は交代演算子を持ちません。
交代可能な演算子の左オペランドの型は、その交代演算子の右オペランドの型と同一で、その逆もまた同様です。
したがって、PostgreSQLで交代演算子を検索する時に必要なものは交代演算子の名前のみになりますので、COMMUTATOR
句でそれのみを与えておけば十分です。
インデックスや結合句で使用される演算子では交代演算子の情報を提供することが必須です。
これにより、問い合わせオプティマイザがその句を他の種類の実行計画で必要とされる形式に「ひっくり返す」ことができるためです。
例えば、tab1.x = tab2.y
のようなWHERE句を持った問い合わせを考えてみます。
ここでtab1.x
とtab2.y
はユーザ定義型で、tab2.y
にはインデックスが付いていると仮定します。
オプティマイザは、この句をtab2.y = tab1.x
という形にひっくり返す方法を知らない限り、インデックススキャンを生成できません。
インデックススキャン機構は演算子の左側にインデックス付けされた列があることを想定しているためです。
PostgreSQLは簡単にこの変形が有効であると前提しません。
=
演算子の作成者がこれが有効であることを、交換演算子情報を持つ演算子であると印付けて指定しなければなりません。
自己交代演算子を定義する場合は、単にそれを指定するだけです。 交代演算子の対を定義する場合は少し複雑になります。 最初に他の未定義のものを参照するものをどう定義するのかということが問題となります。 この問題には下記の2つの解決方法があります。
1つ目の方法は、最初の演算子を定義する際にCOMMUTATOR
句を省略し、2番目の演算子の定義では、COMMUTATOR
句に最初の演算子を与えるという方法です。
PostgreSQLは交代演算子が対になっていることがわかっていますので、2番目の定義を見た時に、自動的に最初の定義に戻ってその未定義になっているCOMMUTATOR
句を設定します。
もう1つの方法は、両方の定義にCOMMUTATOR
句を含めるというもっと素直な方法です。
PostgreSQLは最初の定義を処理する際に、COMMUTATOR
が存在しない演算子を参照していることがわかると、システムはその演算子用の仮のエントリをシステムカタログに作成します。
この仮エントリには、PostgreSQLがこの時点で推定できる、演算子名、左オペランドの型、右オペランドの型、および結果の型についてのみの有効なデータが入ります。
最初の演算子のカタログエントリはこの仮エントリに結び付きます。
この後、2番目の演算子が定義されたら、システムはその仮エントリに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
操作が挿入されることがありますので、この現象は思ったより頻繁に起こります。
否定子の対は、上記の交代演算子のペアで説明した方法と同じ方法で定義することができます。
RESTRICT
RESTRICT
句が与えられた場合、それは、その演算子用の制限選択評価関数を指定します。
(演算子名ではなく関数名であることに注意してください。)
RESTRICT
句はboolean
型を返す二項演算子に対してのみ有効です。
制限選択評価の目的は、現在の演算子と特定の定数値についてのWHERE
句
column OP constant
の条件を満たすテーブル内の行の割合を推測することです。
この形式を持ったWHERE
句によって、どのくらいの行が除外されるのかを通知することで、オプティマイザの手助けをします。
(定数値が左項にあったら何が起こるかという疑問が生じるかもしれませんが、それはCOMMUTATOR
が存在する理由の1つでもあります。)
新しい制限選択評価関数の記述方法は本章の内容を超えていますが、幸いなことに、数多いユーザ定義の演算子に対し通常いくつかのシステム標準の評価関数を使用すれば事足ります。 システム標準の制限評価関数には下記のものがあります。
= 用のeqsel |
<> 用のneqsel |
< 用のscalarltsel |
<= 用のscalarlesel |
> 用のscalargtsel |
>= 用のscalargesel |
非常に高いもしくは低い選択性を所有する演算子が、まったく等しいか等しくないかにかかわらず、eqsel
またはneqsel
を使用しないことも往々にして可能です。
例えば、近似等号用の幾何演算子はテーブルのエントリの小部分にのみに合致すると仮定してeqsel
を使用します。
範囲比較のために数値スカラに変換することに多少の有意性があるデータ型を比較するために、scalarltsel
、scalarlesel
、scalargtsel
、scalargesel
を使用することも可能です。
できればsrc/backend/utils/adt/selfuncs.c
のconvert_to_scalar()
のルーチンで理解できるところにデータ型を追加してください
(今後、このルーチンはpg_type
システムカタログの列で識別された、データ型ごとの関数で置き換えられなければなりませんが、まだ行われていません)。
これを行わなくても動きますが、オプティマイザは本来の推測機能を十分発揮することができません。
さらにsrc/backend/utils/adt/geo_selfuncs.c
には、幾何演算子に対する選択評価関数areasel
、positionsel
、contsel
があります。
本章の執筆時点では、これらは単なるスタブですが、ともかく使いたい(あるいは改良したい)こともあるでしょう。
JOIN
JOIN
句が与えられた場合、それはその演算子用の結合選択評価関数の名前を指定します。
(これが演算子名ではなく関数名であることに注意してください。)
JOIN
句はboolean
型を返す二項演算子に対してのみ有効です。
結合選択評価の目的は、現在の演算子について、WHERE
句
table1.column1 OP table2.column2
を満たすテーブルの組み合わせの行の割合を推測することです。
RESTRICT
句の使用と同様、これはいくつかの取り得る結合手順のうち、どれが最も仕事量が少ないように考えられるのかをオプティマイザに計算させることで、大きなオプティマイザへの援助となります。
以前と同様、本章でも結合選択評価関数の作成方法は説明しません。 しかし適用できるものがあれば、単に標準の評価関数を使用することをお勧めします。
= 用のeqjoinsel |
<> 用のneqjoinsel |
< 用のscalarltjoinsel |
<= 用のscalarlejoinsel |
> 用のscalargtjoinsel |
>= 用のscalargejoinsel |
2次元面積を基にした比較用のareajoinsel |
2次元位置を基にした比較用のpositionjoinsel |
2次元包含関係を基にした比較用のcontjoinsel |
HASHES
HASHES
句が存在する場合、それはシステムに対して、この演算子に基づいた結合にハッシュ結合方法を使っても問題がないことを伝えます。
HASHES
句はboolean
型を返す二項演算子にのみ有効です。
実際には、この演算子はあるデータ型またはデータ型の組み合わせの等価性を表現しなければなりません。
ハッシュ結合の基礎となっている仮定は、結合演算子は左項と右項の値が同じハッシュコードを持つ時にのみ真を返すことができるということです。
2つの値が異なるハッシュのバケットに置かれた場合、結合演算子の結果が必ず偽であるという仮定を、結合は暗黙的に行い、それらを比べることをしません。
したがって、何らかの等価性を表さない演算子にHASHES
句を指定することはまったく意味がありません。
ほとんどの場合、両辺に同一のデータ型をとる演算子に対してハッシュ機能をサポートすることが現実的です。
しかし時として、2つ以上のデータ型に対して互換的なハッシュ関数、つまり、値自体が異なる表現形態を持っていたとしても「等しい」値に対して同一のハッシュコードを生成する関数を設計することもできます。
例えば、サイズが異なる整数に対するハッシュでは、この性質を調整することで大変単純になります。
HASHES
印を付けるためには、結合演算子はハッシュインデックスの演算子族内になければなりません。
演算子を作成する時には参照する演算子族がまだ存在しませんので、演算子の作成時にこれは強制されていません。
しかし、演算子族が存在しない場合に、この演算子をハッシュ結合で使用しようとすると、実行時に失敗します。
システムは、演算子の入力データ型用のデータ型特有のハッシュ関数を検索するために、演算子族を必要とします。
もちろん、演算子族を作成する前に適切なハッシュ関数を作成しなければなりません。
ハッシュ関数を準備する時には注意が必要です。
マシンに依存することから、ハッシュ結合が適切な処理を行わずに失敗することがあるからです。
例えば、データ型が不要な部分を埋めるビットを持つ可能性がある構造体である場合、(推奨する戦略である、他の演算子と関数を作成して、不要なビットが常にゼロになることを保証しない限り、)その構造体全体を単にhash_any
に渡すことはできません。
この他の例として、IEEE浮動小数点標準を満たすマシンでは、マイナス0とプラス0は異なる値(異なるビット列)になりますが、この比較は等価と定義されます。
浮動小数点数値がマイナス0を持つ可能性があるのであれば、それがプラス0と同じハッシュコードを確実に生成するような処置が必要です。
ハッシュ結合可能な演算子は、同一演算子族内に存在する交代演算子を持たなければなりません。 (2つの入力データ型が同じ場合はその演算子自体が交代演算子となります。異なる場合は関連する等価性演算子となります。) これを満たさないと、演算子の使用時にプランナエラーが発生します。 また、複数のデータ型をサポートするハッシュ演算子族に対して、データ型の組み合わせすべてに対する等価性演算子を持たせることを推奨します(必要ではありません)。 これにより、より優れた最適化が可能になります。
ハッシュ結合可能演算子の基となる関数はimmutableもしくはstableでなければなりません。 volatileの場合、システムはその演算子を決してハッシュ結合に使用しません。
ハッシュ結合可能演算子の基となる関数が厳密(strict)な場合、その関数は完全、つまり2つの非NULL入力に対して、真または偽を返し、決してNULLを返さないものである必要があります。
この規則に従わないと、IN
操作におけるハッシュ最適化は間違った結果を生成する可能性があります。
(特に、標準に従うとNULLが正しい答えになるところでIN
は偽を返すかもしれません。
もしくは、NULLという結果に対する準備をしていないといったエラーを生成するかもしれません。)
MERGES
MERGES
句が存在する場合、それはシステムに対して、この演算子に基づいた結合にマージ結合方法を使っても問題がないことを伝えます。
MERGES
句はboolean
型を返す二項演算子にのみ有効です。
実際には、演算子がデータ型またはデータ型の組み合わせの等価性を表すものであることが必要です。
マージ結合は、左側のテーブル、右側のテーブルを順序よくソートし、並列にスキャンするという考えに基づいています。
したがって、両データ型には完全な順序付け機能が必要であり、結合演算子はソート順で「同じ場所」にある値の対のみを成功したものとするものである必要があります。
実際問題として、これは、結合演算子は等価性のような振舞いをしなければならないことを意味しています。
しかし、マージ結合は論理的な互換性を持つ別の2つのデータ型を取ることができます。
例えば、smallint
対integer
の等価性演算子はマージ結合が可能です。
両方のデータ型を論理的な互換性を保つ順番にソートする演算子のみが必要です。
MERGES
印を付けるためには、結合演算子は、btree
インデックス演算子族の等価性メンバとして存在しなければなりません。
演算子を作成する時には参照する演算子族がまだ存在しませんので、演算子の作成時にこれは強制されていません。
しかし、対応する演算子族が存在しない限り、実際にマージ結合に使用されることはありません。
このように、MERGES
フラグは、プランナが対応する演算子族を検索すべきかどうかを決定する際のヒントとして動作します。
マージ結合可能な演算子は、同一演算子族内に存在する交代演算子を持たなければなりません。
(2つの入力データ型が同じ場合はその演算子自体が交代演算子となります。異なる場合は関連する等価性演算子となります。)
これを満たさないと、演算子の使用時にプランナエラーが発生します。
また、複数のデータ型をサポートするbtree
演算子族に対して、データ型の組み合わせすべてに対する等価性演算子を持たせることを推奨します(必要ではありません)。
これにより、より優れた最適化が可能になります。
マージ結合可能演算子の背後にある関数はimmutableもしくはstableでなければなりません。 volatileの場合、システムはその演算子を決してマージ結合に使用しようとはしません。