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

著者: Tom Lane 氏

PostgreSQLの演算子定義には、システムに演算子がどうふるまうかに関する有効な事を伝える、幾つかのオプション句を持つ事ができます。これらの句はその演算子を使用する問い合わせ実行の際に、これらの句の情報により、かなりの速度向上がなされるので、適当な時には常に適応できる状態にしておかなければなりません。しかし、適応する際、それらが正しい事を確認しなければいけません。最適化用の句を間違って使用すると、バックエンドのクラッシュ、不思議な間違った出力、その他有害な事が起こります。最適化用の句について解らなければ、使用しなくても構いません。 使用された時よりも問い合わせの実行が遅くなるかもしれないというだけです。

最適化用の句は、PostgreSQLの今後のバージョンで更に追加される可能性があります。ここで記述したものは全て、バージョン 7.3.2 で有効なものです。

11.3.1. COMMUTATOR句

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

交代された演算子の左オペランドの型は、その交代演算子の右オペランドの型と同一で、その逆も又同様です。したがって、PostgreSQLで演算子を検索する時に必要なものは交代演算子の名前のみであり、COMMUTATOR句ではそれのみが与えられていればよいです。

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

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

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

11.3.3. RESTRICT句

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

column OP constant

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

新しい制限選択評価関数の記述方法はこの章の内容を越えていますが、幸いな事に、大抵の場合システムが持つ標準的な評価関数を使って、多くの自作演算子用が作成できます。標準的な制限評価関数には下記のものがあります。

eqsel for =
neqsel for <>
scalarltsel for < or <=
scalargtsel for > or >=

これらがカテゴリであることは少し奇妙に思えるかもしれませんが、これらには歴とした意味があります。>=は一般的にテーブル内の行の小さな部分を受け付けます。 >=は一般的に小さな部分を除きます。 <は、指定した定数がテーブル列(これはANALYZE によって収集される情報で、選択評価関数で使用できるように作成されます。 )のとる値の範囲のどの辺りにあるのかに依存する量の部分を受け付けます。<=< よりも少しだけ大きな部分を受け付けます。この差は比較に用いた定数と同じ部分のためですが、特に大雑把な推測以上のことを行なうのは適切ではありませんので、区別する価値がないといえる位近い値です。>>=についても同様な事がいえます。

非常に高い/低い選択性をもつ演算子に、全く等しい場合/等しくない場合で、eqselneqselを使用しないでおく事も可能です。例えば、ほぼ等しい幾何演算子ではテーブルのエントリの小部分にのみに合うものと仮定して eqsel を使用します。

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

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

11.3.4. JOIN 句

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

table1.column1 OP table2.column2

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

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

eqjoinsel for =
neqjoinsel for <>
scalarltjoinsel for < or <=
scalargtjoinsel for > or >=
areajoinsel for 2D area-based comparisons
positionjoinsel for 2D position-based comparisons
contjoinsel for 2D containment-based comparisons

11.3.5. HASHES句

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

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

実際は、論理的な等価性はあまり十分ではなく、演算子は純粋にビット単位の等価性を表すものの方が好ましいです。なぜならば、ハッシュ関数は、ビットの意味は関係なく、メモリ内の値表現を使って計算されるからです。例えば、時間間隔の等価性はビット単位での等価性ではなく、間隔の等価性演算子は、二つの時間間隔がその終了時刻が異なっていた場合でも、期間が同一であれば、その時間間隔は等価であるとみなします。これは、間隔フィールドとの間で = を使った結合は、ハッシュ結合を実装した場合とそれ以外で実装した場合とで、異なる結果をもたらすことを意味しています。 それは、合うべきペアの多くの部分は異なる値にハッシュされ、ハッシュ結合時に比較されなくなるために起こります。しかし、オブティマイザが他種類の結合を使用する事を選んだ場合、等価性演算子が同一で あるとした全てのペアが見つかります。このような矛盾は好ましくありませんので、間隔の等価性をハッシュ可能とはしません。

マシンに依存することから、ハッシュ結合が適切な処理を行なわずに失敗することがあります。例えば、データ型が不要な部分を埋めたビットを持つ可能性がある構造である場合、その等価性演算子にHASHES 句をつけることは安全ではありません。(他の演算子を不要なビットが常に0になるように作成している場合は状況が変わる場合があります。)この他の例として、浮動小数点データ型でハッシュ結合を使用するには安全ではない場合があります。IEEE 浮動小数点標準をみたすマシンではマイナス0とプラス0は異なる値(異なるビット列)となりますが、等価であるものと定義されます。したがって、浮動小数点データ型の等価性演算子にHASHES 句を付けると、マイナス0とプラス0はハッシュ結合では多分一致されませんが、他の結合処理では一致するものとされます。

つまり、memcmp() で実装された(できた)等価性演算子にのみにHASHES 句を使用するべきです。

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

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

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

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

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

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

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