★PostgreSQLカンファレンス2024 12月6日開催/チケット販売中★
他のバージョンの文書 16 | 15 | 14 | 13 | 12 | 11 | 10 | 9.6 | 9.5 | 9.4 | 9.3 | 9.2 | 9.1 | 9.0 | 8.4 | 8.3 | 8.2 | 8.1 | 8.0 | 7.4 | 7.3 | 7.2

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

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

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

37.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つの解決方法があります。

  • 1つ目の方法は、最初の演算子を定義する際にCOMMUTATOR句を省略し、2番目の演算子の定義では、COMMUTATOR句に最初の演算子を与えるという方法です。 PostgreSQLは交代演算子が対になっていることがわかっていますので、2番目の定義を見た時に、自動的に最初の定義に戻ってその未定義になっているCOMMUTATOR句を設定します。

  • もう1つの方法は、両方の定義にCOMMUTATOR句を含めるというもっと素直な方法です。 PostgreSQLは最初の定義を処理する際に、COMMUTATORが存在しない演算子を参照していることがわかると、システムはその演算子用の仮のエントリをシステムカタログに作成します。 この仮エントリには、PostgreSQLがこの時点で推定できる、演算子名、左オペランドの型、右オペランドの型、および結果の型についてのみの有効なデータが入ります。 最初の演算子のカタログエントリはこの仮エントリに結び付きます。 この後、2番目の演算子が定義されたら、システムはその仮エントリに2番目の定義から得られる追加情報を更新します。 更新される前に仮の演算子を使用すると、エラーメッセージが出力されます。

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

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

37.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システムカタログの列で識別された、データ型ごとの関数で置き換えられなければなりませんが、まだ行われていません)。 これを行わなくても動きますが、オプティマイザは本来の推測機能を十分発揮することができません。

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

37.13.4. JOIN

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

table1.column1 OP table2.column2

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

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

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

37.13.5. 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という結果に対する準備をしていないといったエラーを生成するかもしれません。)

37.13.6. MERGES

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

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

MERGES印を付けるためには、結合演算子は、btreeインデックス演算子族の等価性メンバとして存在しなければなりません。 演算子を作成する時には参照する演算子族がまだ存在しませんので、演算子の作成時にこれは強制されていません。 しかし、対応する演算子族が存在しない限り、実際にマージ結合に使用されることはありません。 このように、MERGESフラグは、プランナが対応する演算子族を検索すべきかどうかを決定する際のヒントとして動作します。

マージ結合可能な演算子は、同一演算子族内に存在する交代演算子を持たなければなりません。 (2つの入力データ型が同じ場合はその演算子自体が交代演算子となります。異なる場合は関連する等価性演算子となります。) これを満たさないと、演算子の使用時にプランナエラーが発生します。 また、複数のデータ型をサポートするbtree演算子族に対して、データ型の組み合わせすべてに対する等価性演算子を持たせることを推奨します(必要ではありません)。 これにより、より優れた最適化が可能になります。

注記

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