pg_hint_plan

# pg_hint_plan 1.0.0

1. 名前
2. 概要
3. 機能説明
4. インストール
5. アンインストール
6. 使用例
7. 使用上の注意と制約
8. 動作環境
9. 関連項目
10. Appendix A. ヒント一覧

## 名前

pg_hint_plan -- 特殊なコメント中にヒント情報を記述することで、SQL文やGUCパラメータを変えずに実行計画を制御します。

## 概要

PostgreSQLはコストベースオプティマイザを採用しており、SQL文と統計情報を元に可能なアクセスパスのコストを見積もり、最もコストの低い実行計画を選択します。オプティマイザは可能な限りよい実行計画を作成使用としますが、カラム間の相関関係などは考慮しないため、複雑なクエリでは常に最適なプランが選択されるとは限りません。

pg_hint_planを用いると、ヒントを記述したブロックコメントをSQL文の前に加えることで、SQL文やGUCパラメータを変更することなく実行計画を制御することができます。

## 機能説明

pg_hint_planの機能について説明します。

### ヒントの記述方法

ヒントはクエリ文字列の先頭のブロックコメント内に記述してください。ブロックコメントをヒントとして認識させるには、ブロックコメントの開始直後にプラス（+）を指定する必要があります。ヒントの対象は、カッコ内にオブジェクト名または別名(別名)で指定してください。オブジェクト名は、スペース、タブ、または改行のいずれかで区切って指定してください。

以下の例では、HashJoinとSeqScanヒントにより、pgbench_accountsテーブルに対するSeq Scanの結果をHash Joinする実行計画が選択されています。

    postgres=# /*+
    postgres*#    HashJoin(a b)
    postgres*#    SeqScan(a)
    postgres*#  */
    postgres-# EXPLAIN SELECT *
    postgres-#    FROM pgbench_branches b
    postgres-#    JOIN pgbench_accounts a ON b.bid = a.bid
    postgres-#   ORDER BY a.aid;
                                          QUERY PLAN
    ---------------------------------------------------------------------------------------
     Sort  (cost=31465.84..31715.84 rows=100000 width=197)
       Sort Key: a.aid
       ->  Hash Join  (cost=1.02..4016.02 rows=100000 width=197)
             Hash Cond: (a.bid = b.bid)
             ->  Seq Scan on pgbench_accounts a  (cost=0.00..2640.00 rows=100000 width=97)
             ->  Hash  (cost=1.01..1.01 rows=1 width=100)
                   ->  Seq Scan on pgbench_branches b  (cost=0.00..1.01 rows=1 width=100)
    (7 rows)

    postgres=#

### ヒントのグループ

pg_hint_planで使えるヒントは、スキャン方式と結合方式、結合順序、GUCパラメータの4グループに分けられます。各グループの具体的なヒントは、ヒント一覧を参照してください。

#### スキャン方式

あるオブジェクトでどのスキャン方式を選択するかを指定できるヒントのグループで、「SeqScan」や「IndexScan」などが含まれます。

スキャン方式を指定できるオブジェクトは、通常のテーブル、継承テーブル、UNLOGGEDテーブル、一時テーブル、システムカタログです。スキャン方式を指定できないオブジェクトは、外部テーブル、テーブル関数、VALUESコマンド結果、CTE、ビュー、副問い合わせ結果です。

特定のオブジェクトについてあるスキャン方式を選択して欲しい場合は、そのスキャン方式のヒントと、対象となるオブジェクトの名前を指定してください。逆に、特定のオブジェクトについてあるスキャン方式を選択して欲しくない場合は、Noで始まるヒントを指定してください。同じオブジェクトに対して複数のスキャン方式のヒントを指定した場合は、最後に指定したヒントが適用されます。

#### 結合方式

あるオブジェクトの組み合わせでどの結合方式を選択するかを指定できるヒントのグループで、「MergeJoin」や「NestLoop」などが含まれます。

結合方式を指定できるオブジェクトは、通常のテーブル、継承テーブル、UNLOGGEDテーブル、一時テーブル、外部テーブル、システムカタログ、テーブル関数、VALUESコマンド結果、CTEです。結合方式を指定できないオブジェクトは、ビュー、副問い合わせ結果です。

特定のオブジェクトの組み合わせについてある結合方式を選択して欲しい場合は、その結合方式のヒントと、対象となる2つ以上のオブジェクトの名前を指定してください。逆に、特定のオブジェクトの組み合わせについてある結合方式を選択して欲しくない場合は、Noで始まるヒントを指定してください。同じオブジェクトの組み合わせに対して複数の結合方式のヒントを指定した場合は、最後に指定したヒントが適用されます。

#### 結合順序

あるオブジェクトの組み合わせでどのような順番で結合するかを指定できるヒントのグループで、「Leading」のみが含まれます。

結合順序を指定できるオブジェクトは結合方式と同じです。

先に結合して欲しいオブジェクトから順にオブジェクト名または別名を指定してください。複数の結合順序のヒントを指定した場合は、最後に指定したヒントが適用されます。クエリ中に複数の問い合わせブロックがあり、それぞれに結合順を指定したい場合は、それぞれの結合順を1つのLeadingヒントに連続して指定してください。

#### GUCパラメータ

そのクエリの実行計画を作成している間だけGUCパラメータを変更できるヒントのグループで、「Set」のみが含まれます。

設定したいGUCパラメータとそのパラメータの値を指定してください。SETコマンドで指定できるGUCパラメータならば全て指定できますが、効果があるのは問い合わせ計画のGUCパラメータのみです。同じGUCパラメータに対して複数のGUCパラメータのヒントを指定した場合は、最後に指定したヒントが適用されます。

Setヒントにpg_hint_planのGUCパラメータを指定することはできますが、期待通りの動作をしないため、指定しないことをおすすめします。指定した場合の実際の動作は、使用上の注意と制約を参照してください。

### pg_hint_planのGUCパラメータ

pg_hint_planの動作を制御するGUCパラメータを以下に記述します。

GUCパラメータ | 説明 | デフォルト値
--- | --- | ---
pg_hint_plan.enable_hint | pg_hint_planの機能を有効または無効にします。 | on
pg_hint_plan.debug_print | pg_hint_planのデバッグ出力を有効にします。メッセージはLOGメッセージレベルで出力されますので、デフォルトではサーバログに出力され、クライアントには渡されません。 | off
pg_hint_plan.parse_messages | 指定したヒントを解釈できなかった場合に、どのメッセージ階層でログを出力するかを指定します。有効な値は、debug5、debug4、debug3、debug2、debug1、log、info、notice、warning、またはerrorです。fatalとpanicは指定できません。 | info

PostgreSQL 9.1の環境でこれらのパラメータをpostgresql.confファイルで設定するには、custom_variable_classesにpg_hint_planを加える必要があります。 典型的な使用例は以下のようになります。

    # postgresql.conf
    shared_preload_libraries = 'pg_hint_plan'

    custom_variable_classes = 'pg_hint_plan'    # 9.2以降は廃止されたため記述不要
    pg_hint_plan.parse_messages = 'debug2'

PostgreSQL 9.2以降ではcustom_variable_classesは廃止されているため、pg_hint_planのGUCパラメータを標準のGUCパラメータと同様に記述することができます。

## インストール

pg_hint_planのインストール方法について説明します。

### ビルド

pg_hint_planをソースコードからビルドする場合、pg_hint_planのソースを展開したディレクトリでmake → make installの順に実行してください。make installはPostgreSQLをインストールしたOSユーザで実行してください。なお、pg_hint_planのビルドにはpgxsを使用するので、RPM版のPostgreSQLを使用している環境では、postgresql-devel パッケージが必要です。

以下にビルドの例を示します。

    $ tar xzvf pg_hint_plan-1.0.0.tar.gz
    $ cd pg_hint_plan-1.0.0
    $ make
    $ su
    # make install

### pg_hint_planのロード

特定のセッションでのみpg_hint_planを使う場合は、以下の例のようにpg_hint_planの共有ライブラリをLOADコマンドでロードしてください。一般ユーザで利用する場合は$libdir/pluginsにもインストールする必要があるので注意して下さい。

    postgres=# LOAD 'pg_hint_plan';
    LOAD
    postgres=#

全てのセッションでpg_hint_planを有効にするには、shared_preload_libraries GUCパラメータに'pg_hint_plan'を追加してからサーバを再起動して下さい。

## アンインストール

pg_hint_planをアンインストールするには、pg_hint_planのソースを展開したディレクトリでmake uninstallを実行してください。make uninstallはPostgreSQLをインストールしたOSユーザで実行してください。

以下にアンインストールの例を示します。

    $ cd pg_hint_plan-1.0.0
    $ su
    # make uninstall

## 使用例

### スキャン方式

スキャン方式のヒントでは、スキャン対象のテーブルを指定します。IndexScanヒントで特定のインデックスを使用したい場合は、オプションでインデックスも指定できます。以下の例では、table1はSeq Scan、table2は主キーインデックスでのIndex Scanを選択させています。

    postgres=# /*+
    postgres*#     SeqScan(t1)
    postgres*#     IndexScan(t2 t2_pkey)
    postgres*#  */
    postgres-# SELECT * FROM table1 t1 JOIN table table2 t2 ON (t1.key = t2.key);
    ...

### 結合方式/結合順序

結合方式や結合順序のヒントでは、結合対象のテーブルのリストを指定します。以下の例では、table1とtabele2を直接結合する場合はNested Loopを、table1とtable2とtable3を結合する場合はMerge Joinを選択させています。また、コスト見積もりによってはtable1とtable2が直接結合されない場合がありえるため、table1とtable2を結合してからtable3を結合するようにLeadingヒントを併用しています。

    postgres=# /*+
    postgres*#     NestLoop(t1 t2)
    postgres*#     MergeJoin(t1 t2 t3)
    postgres*#     Leading(t1 t2 t3)
    postgres*#  */
    postgres-# SELECT * FROM table1 t1
    postgres-#     JOIN table table2 t2 ON (t1.key = t2.key)
    postgres-#     JOIN table table3 t3 ON (t2.key = t3.key);
    ...

### GUCパラメータ

GUCパラメータのヒントでは、GUCパラメータと値のペアを指定します。以下の例では、このクエリの実行計画を作成する間だけrandom_page_costを2.0に変更しています。

    postgres=# /*+
    postgres*#     Set(random_page_cost 2.0)
    postgres*#  */
    postgres-# SELECT * FROM table1 t1 WHERE key = 'value';
    ...

## 使用上の注意と制約

pg_hint_planを使用する際には、以下の注意と制約があります。

### ヒントの指定方法

ヒントの記述位置
  クエリの前に複数のブロックコメントを記述する場合は、最初のブロックコメントにのみヒントを記述してください。二番目以降のブロックコメントは、ヒントと見なされず無視されます。以下の例では、HashJoin(a b)とSeqScan(a)がヒントと見なされ、IndexScan(a)とMergeJoin(a b)は無視されています。

      postgres=# /*+
      postgres*#    HashJoin(a b)
      postgres*#    SeqScan(a)
      postgres*#  */
      postgres-# /*+ IndexScan(a) */
      postgres-# EXPLAIN SELECT /*+ MergeJoin(a b) */ *
      postgres-#    FROM pgbench_branches b
      postgres-#    JOIN pgbench_accounts a ON b.bid = a.bid
      postgres-#   ORDER BY a.aid;
                                            QUERY PLAN
      ---------------------------------------------------------------------------------------
       Sort  (cost=31465.84..31715.84 rows=100000 width=197)
         Sort Key: a.aid
         ->  Hash Join  (cost=1.02..4016.02 rows=100000 width=197)
               Hash Cond: (a.bid = b.bid)
               ->  Seq Scan on pgbench_accounts a  (cost=0.00..2640.00 rows=100000 width=97)
               ->  Hash  (cost=1.01..1.01 rows=1 width=100)
                     ->  Seq Scan on pgbench_branches b  (cost=0.00..1.01 rows=1 width=100)
      (7 rows)

      postgres=#

オブジェクト名の引用符付け
  ヒントに記述するオブジェクト名や別名が閉じ括弧（)）、二重引用符（"）、空白（スペース、タブ、改行のいずれか）を含む場合は、通常のSQL文で使う場合と同じように二重引用符(")で囲んでください。二重引用符を含むオブジェクト名は、二重引用符で括ったうえで二重引用符を二重引用符でエスケープしてください(例: 「quoted"table"name」→「"quoted""table""name"」)。
同一名称テーブルの区別
  スキーマ違いや同一テーブルの複数回使用などでクエリ中に同一名称のテーブルが複数回出現する場合は、テーブルに別名をつけてそれぞれのテーブルを区別してください。以下の例の1つ目のSQL文では、HashJoin(t1 t1)をヒントに指定したとき、ヒント対象のオブジェクトが特定できずにエラーになっています。2つ目のSQL文では、各テーブルにptやstという別名をつけているため、実行計画作成時にヒントで指定した通りにHash Joinを選択しています。

      postgres=# /*+ HashJoin(t1 t1)*/
      postgres-# EXPLAIN SELECT * FROM s1.t1
      postgres-# JOIN public.t1 ON (s1.t1.id=public.t1.id);
      INFO:  hint syntax error at or near "HashJoin(t1 t1)"
      DETAIL:  Relation name "t1" is ambiguous.
                                  QUERY PLAN
      ------------------------------------------------------------------
       Merge Join  (cost=337.49..781.49 rows=28800 width=8)
         Merge Cond: (s1.t1.id = public.t1.id)
         ->  Sort  (cost=168.75..174.75 rows=2400 width=4)
               Sort Key: s1.t1.id
               ->  Seq Scan on t1  (cost=0.00..34.00 rows=2400 width=4)
         ->  Sort  (cost=168.75..174.75 rows=2400 width=4)
               Sort Key: public.t1.id
               ->  Seq Scan on t1  (cost=0.00..34.00 rows=2400 width=4)
      (8 行)

      postgres=# /*+ HashJoin(pt st) */
      postgres-# EXPLAIN SELECT * FROM s1.t1 st
      postgres-# JOIN public.t1 pt ON (st.id=pt.id);
                                   QUERY PLAN
      ---------------------------------------------------------------------
       Hash Join  (cost=64.00..1112.00 rows=28800 width=8)
         Hash Cond: (st.id = pt.id)
         ->  Seq Scan on t1 st  (cost=0.00..34.00 rows=2400 width=4)
         ->  Hash  (cost=34.00..34.00 rows=2400 width=4)
               ->  Seq Scan on t1 pt  (cost=0.00..34.00 rows=2400 width=4)
      (5 行)

      postgres=#

FROM句にVALUESコマンドを指定した場合の制限
  FROM句にVALUESコマンドを指定した場合は、ヒントのオブジェクト名に「*VALUES*」を指定してください。これは、VALUESの結果に別名を指定しても、PostgreSQL本体側で「*VALUES*」に名称が置き換えられるためです。このため、複数のVALUESを使用する場合は、ヒントの対象を特定できないため、実行計画を制御できません。

### ヒントの適用対象の指定

クエリに明記されていないテーブルへのヒント適用
  ヒントで指定した名称と一致すれば、ビュー定義や関数内クエリなどに出現するテーブルについても、ヒントを指定したクエリ内と同じようにヒントが適用されます。このため、ヒントの適用有無や適用するヒントをそれぞれのテーブルで変えたい場合は、別の別名を指定してください。 以下の例では、ビュー定義で使われている「t1」という別名をSeqScanヒントで指定したことで、表スキャンとビュー経由のスキャンの両方でSeq Scanが選択されています。ビュー定義で使用されている「t1」とは別の別名を実表に指定することで、個別にスキャン方式を制御できます。

      postgres=# CREATE VIEW view1 AS SELECT * FROM table1 t1;
      CREATE TABLE
      postgres=# /*+ SeqScan(t1) */
      postgres=# EXPLAIN SELECT * FROM table1 t1 JOIN view1 t2 ON (t1.key = t2.key) WHERE t2.key = 1;
                                 QUERY PLAN
      -----------------------------------------------------------------
       Nested Loop  (cost=0.00..358.01 rows=1 width=16)
         ->  Seq Scan on table1 t1  (cost=0.00..179.00 rows=1 width=8)
               Filter: (key = 1)
         ->  Seq Scan on table1 t1  (cost=0.00..179.00 rows=1 width=8)
               Filter: (key = 1)
      (5 rows)

      postgres=# /*+ SeqScan(t3) */
      postgres=# EXPLAIN SELECT * FROM table1 t3 JOIN view1 t2 ON (t1.key = t2.key) WHERE t2.key = 1;
                                         QUERY PLAN
      --------------------------------------------------------------------------------
       Nested Loop  (cost=0.00..187.29 rows=1 width=16)
         ->  Seq Scan on table1 t3  (cost=0.00..179.00 rows=1 width=8)
               Filter: (key = 1)
         ->  Index Scan using foo_pkey on table1 t1  (cost=0.00..8.28 rows=1 width=8)
               Index Cond: (key = 1)
      (5 rows)

継承テーブルに対するヒント
  継承テーブルにスキャン方式のヒントを指定する場合は、オブジェクト名として親テーブルの名称または別名を指定してください。全ての子テーブルに同じスキャン方式を選択します。子テーブルごとに別のスキャン方式を指定することはできません。
ビューやルールを使うクエリのヒント
  ルールを定義したテーブルやビューを複数用いるときに、各ビュー内のテーブルの別名やルール書き換え後のクエリのテーブルの別名が重複した場合は、ヒントの対象を区別できません。区別する場合は、各ビュー内のテーブルの別名やルール書き換え後のクエリのテーブルの別名を重複させないでください。
ルールを使うクエリでのヒントの適用範囲
  ルールによるクエリ書き換え後によってクエリが複数になる場合は、先頭のブロックコメントで指定したヒントを全てのクエリで使用します。
マルチステートメントでのヒントの適用範囲
  クエリがマルチステートメントで実行される場合は、先頭のブロックコメントで指定したヒントを全てのクエリで使用します。2つ目以降のクエリに指定したヒントは無視します。psqlコマンドで-cオプションで複数のクエリを指定した場合などにマルチステートメントで実行されます。
IndexOnlyScanヒントの指定(PostgreSQL 9.2以降)
  IndexOnlyScanヒントを指定しても、ヒント対象となるテーブルに複数のインデックスが存在するときは、Index Scanが選択される場合があります。この場合は、IndexOnlyScanヒントに、テーブルだけでなくそのテーブルでIndex Only Scanを選択できるインデックスも指定してください。そのインデックスを使ったIndex Only Scanが選択されます。
NoIndexScanヒントの注意点(PostgreSQL 9.2以降)
  PostgreSQL 9.2以降でNoIndexScanヒントを指定した場合は、Index ScanだけでなくIndex Only Scanも選択されません。

### ヒント指定エラーの扱い

構文エラー
  ヒントの記述に構文上の誤りがあった場合、pg_hint_planは誤った記述より前のヒントのみ有効とし、誤った記述以降のヒントを無視してクエリを実行します。誤りの内容はpg_hint_plan.parse_messagesで指定したレベルでサーバログに記録されます。
  - ヒント名が間違っている。
  - オブジェクト指定が正しく括弧で囲まれていない。
  - オブジェクト名が空白で区切られていない。
オブジェクト指定エラー
  pg_hint_planは、ヒント対象のオブジェクト指定に誤りがあった場合、pg_hint_planは不正なヒントのみを無視し、それ以外のヒントを使ってっクエリを実行します。誤りの内容はpg_hint_plan.parse_messagesで指定したレベルでサーバログに記録されます。誤ったオブジェクト指定の例を以下に示します。

- クエリ中に同じ名称のテーブル名または別名のテーブルがあり、それに対してヒントを指定した。
- 結合方式や結合順のヒントに同じオブジェクト名を複数回指定した。

指定するヒントの種類の重複
  同じオブジェクトに対して同じグループのヒントを重複して指定した場合は、各グループで最後に指定したヒントを使用します。
ネストしたブロックコメント
  pg_hint_planでは、ヒントを指定したブロックコメントにネストしたブロックコメントを含めることができません。ネストしたブロックコメントを含めた場合は、誤った記述に関する情報を出力しますがエラー終了しません。全てのヒントを無視してクエリを実行します。
メッセージの出力レベル
  ヒントに誤りがあった場合に出力されるメッセージのレベルは、基本的にはpg_hint_plan.parse_messagesに指定したレベルです。ただし、ヒントに指定したオブジェクトの長さが識別子の最大長(デフォルトでは63バイト)を超えた際に切り詰めた場合は、NOTICEで出力します。

### 機能制限

標準のGUCパラメータの影響
  FROMリストの数がfrom_collapse_limitの設定値以上の場合、またはFROMリストの数がjoin_collapse_limitの設定値より大きい場合は、結合順序のヒントが無視されます。また、FROMリストの数がgeqo_thresholdの設定値以上の場合は、結合順序のヒント、および結合方式のヒントが無視されます。ヒントが使われるようにするには、これらのGUCパラメータの値を大きくしてください。
ヒントで制御できないケース
  pg_hint_planでは、PostgreSQLのプランナが候補としてあげる事ができない実行計画をヒントに指定しても、その実行計画を生成することはできません。PostgreSQLのプランナが候補としてあげる事ができない実行計画の例を以下に示します。
  - FULL OUTER JOINではNested Loopは候補パスとして扱われません。
  - WHERE句やJOIN条件などに指定されない列のみを含むインデックスは候補パスとして扱われません。
  - 検索条件にctidを指定しない場合はTid Scanは候補パスとして扱われません。
PL/pgSQLにおける制限
  PL/pgSQLでユーザ定義関数を実装する際に、関数定義内の各クエリの先頭にヒントを指定したとしても、そのヒントは無視します。ユーザ定義関数を実行するSELECTコマンドに指定したヒントを使用します。ただし、PL/pgSQLでは、関数定義内で指定したクエリがそのまま実行されるとは限らないため、ヒントを指定した場合の挙動は保証できません。
ECPGにおける制限
  pg_hint_planでは、ECPGで実装したアプリケーションから発行するクエリは、基本的に実行計画を制御できません。これは、CプリプロセッサがCコードに変換するタイミングで、全てのブロックコメントを取り除いてしまうためです。ただし、C言語文字列に格納したクエリをEXECUTEコマンドで実行する動的SQLの場合は、クエリ文字列の先頭にヒントを指定することで実行計画を制御できます。
psqlのフェッチ件数指定
  psqlコマンドのFETCH_COUNT変数に0より大きな整数値を指定すると、pg_hint_planでは実行計画を制御できなくなります。FETCH_COUNT変数に0より大きな整数値を指定すると、ユーザが指定したクエリの先頭に「DECLARE _psql_cursor NO SCROLL CURSOR FOR」が自動的に追加されてクエリが発行されることにより、ヒントがクエリの先頭ではなくなってしまうためです。
ヒントによるフィンガープリントの変化
  pg_hint_planはSQLコメントでヒントを指定するため、SQL文フィンガープリントベースのクエリキャッシュなどでは、ヒントが異なれば別のSQL文として扱われます。pg_stat_statementも9.1では別クエリとして集計しますが、9.2ではクエリ集約機能によりコメントが除去されるので、ヒントのみが異なるクエリは同じクエリとして扱われます。

    postgres=# /*+ MergeJoin(a *VALUES*) */
    postgres-# EXPLAIN SELECT *
    postgres-#    FROM pgbench_accounts a
    postgres-#    JOIN (VALUES (1,1),(2,2)) v (vid, vbalance) ON a.aid = v.vid
    postgres-#   ORDER BY a.aid;
                                                      QUERY PLAN
    ---------------------------------------------------------------------------------------------------------------
     Merge Join  (cost=0.04..4497.33 rows=2 width=105)
       Merge Cond: (a.aid = "*VALUES*".column1)
       ->  Index Scan using pgbench_accounts_pkey on pgbench_accounts a  (cost=0.00..4247.26 rows=100000 width=97)
       ->  Sort  (cost=0.04..0.04 rows=2 width=8)
             Sort Key: "*VALUES*".column1
             ->  Values Scan on "*VALUES*"  (cost=0.00..0.03 rows=2 width=8)
    (6 行)

    postgres=#

Setヒントの制限
  Setヒントにpg_hint_planのGUCパラメータを指定することはできますが、期待通りの動作をしないため、指定しないことをおすすめします。指定した場合の実際の動作を、以下に示します。
  - pg_hint_plan.enable_hintおよびpg_hint_plan.debug_printを指定した場合は、無視されます。
  - pg_hint_plan.parse_messagesを指定した場合は、構文エラーと一部のSetヒントのエラーについてはクエリ開始時の設定レベルで出力され、それ以外のメッセージについてはSetヒントで指定したレベルで出力されます。

## 動作環境

PostgreSQL
  バージョン 9.1.4、9.2.1
動作検証済みOS
  RHEL 6.1

## 関連項目

### PostgreSQLドキュメント

EXPLAIN SET サーバの設定