ヒント句の記述について

前のエントリでこんなことを書きました。


テーブルの結合順序を制御したい!
http://d.hatena.ne.jp/nijimass/20080526/1211806659

LEADINGヒントだと
「ヒント句追加しただけだし、もし間違っててもヒント句なので無視されます!」
とか言うと、実行確認レベルで済む可能性があることです!
# ウソです。コード触ってる以上、ちゃんとテストしましょうね。


ヒント句の特徴として、間違ったヒントを書いてもエラーを返さずに、
無視されて、SQL自体は正常に実行されるというものがあります。
ということで、よく聞く話を。




ヒントの指定で間違いやすいポイントとして、表名の指定があります。
SQL文内で表の別名を使用している場合、
使用している別名でヒントの指定をしないと、
ヒントが有効になりません。


Oracle Database パフォーマンス・チューニング・ガイド 10g リリース2(10.2)
16 オプティマイザ・ヒントの使用方法
http://otndnld.oracle.co.jp/document/products/oracle10g/102/doc_cd/server.102/B19207-01/hintsref.html#964421

アクセスする表は、文に指定する場合と同じように正確に指定してください。
文が表の別名を使用している場合、表の名前ではなく、表の別名をヒントで使用する必要があります。
スキーマ名が文中にある場合は、ヒント内の表名にそのスキーマ名を入れないでください。


「ヒントがきかないんです!」っていう問い合わせは大体コレか、
そもそも、そういう結合をオプティマイザがしてないのどちらか。


確認してみましょうね。
まず、ベースとなるSQL文です。

SQL> EXPLAIN PLAN FOR
 2  SELECT E.ENAME ,E.SAL ,D.DNAME ,S.GRADE
 3    FROM EMP E
 4        ,DEPT D
 5        ,SALGRADE S
 6   WHERE E.DEPTNO = D.DEPTNO
 7     AND E.SAL > S.LOSAL
 8     AND E.SAL <= S.HISAL;

解析されました。

SQL> set linesize 120
SQL> set pagesize 10000
SQL> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------
Plan hash value: 721498669

-----------------------------------------------------------------------------------------
| Id  | Operation                    | Name     | Rows  | Bytes | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |          |     1 |    94 |     9  (23)| 00:00:01 |
|   1 |  NESTED LOOPS                |          |     1 |    94 |     9  (23)| 00:00:01 |
|   2 |   MERGE JOIN                 |          |     1 |    72 |     8  (25)| 00:00:01 |
|   3 |    SORT JOIN                 |          |     5 |   195 |     4  (25)| 00:00:01 |
|   4 |     TABLE ACCESS FULL        | SALGRADE |     5 |   195 |     3   (0)| 00:00:01 |
|*  5 |    FILTER                    |          |       |       |            |          |
|*  6 |     SORT JOIN                |          |    12 |   396 |     4  (25)| 00:00:01 |
|   7 |      TABLE ACCESS FULL       | EMP      |    12 |   396 |     3   (0)| 00:00:01 |
|   8 |   TABLE ACCESS BY INDEX ROWID| DEPT     |     1 |    22 |     1   (0)| 00:00:01 |
|*  9 |    INDEX UNIQUE SCAN         | PK_DEPT  |     1 |       |     0   (0)| 00:00:01 |
-----------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

  5 - filter("E"."SAL"<="S"."HISAL")
  6 - access("E"."SAL">"S"."LOSAL")
      filter("E"."SAL">"S"."LOSAL")
  9 - access("E"."DEPTNO"="D"."DEPTNO")

Note
-----
  - dynamic sampling used for this statement

28行が選択されました。


EMP -> SALGRADE -> DEPT の順で結合してます。
これを EMP -> DEPT -> SALGRADE の順にしたい場合、
このSQLでは、FROM句 で表の別名を指定していますので、
下記のように記述します。

SQL> EXPLAIN PLAN FOR
 2  SELECT /*+ LEADING ( E D S ) */
 3         E.ENAME ,E.SAL ,D.DNAME ,S.GRADE
 4    FROM EMP E
 5        ,DEPT D
 6        ,SALGRADE S
 7   WHERE E.DEPTNO = D.DEPTNO
 8     AND E.SAL > S.LOSAL
 9     AND E.SAL <= S.HISAL;

解析されました。

SQL> set linesize 120
SQL> set pagesize 10000
SQL> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------
Plan hash value: 2472009151

---------------------------------------------------------------------------------
| Id  | Operation            | Name     | Rows  | Bytes | Cost (%CPU)|Time      |
---------------------------------------------------------------------------------
|   0 | SELECT STATEMENT     |          |     1 |    94 |    12  (25)| 00:00:01 |
|   1 |  MERGE JOIN          |          |     1 |    94 |    12  (25)| 00:00:01 |
|   2 |   SORT JOIN          |          |    12 |   660 |     8  (25)| 00:00:01 |
|*  3 |    HASH JOIN         |          |    12 |   660 |     7  (15)| 00:00:01 |
|   4 |     TABLE ACCESS FULL| EMP      |    12 |   396 |     3   (0)| 00:00:01 |
|   5 |     TABLE ACCESS FULL| DEPT     |     4 |    88 |     3   (0)| 00:00:01 |
|*  6 |   FILTER             |          |       |       |            |          |
|*  7 |    SORT JOIN         |          |     5 |   195 |     4  (25)| 00:00:01 |
|   8 |     TABLE ACCESS FULL| SALGRADE |     5 |   195 |     3   (0)| 00:00:01 |
---------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

  3 - access("E"."DEPTNO"="D"."DEPTNO")
  6 - filter("E"."SAL"<="S"."HISAL")
  7 - access(INTERNAL_FUNCTION("E"."SAL")>INTERNAL_FUNCTION("S"."LOSAL")
             )
      filter(INTERNAL_FUNCTION("E"."SAL")>INTERNAL_FUNCTION("S"."LOSAL")
             )

Note
-----
  - dynamic sampling used for this statement

29行が選択されました。


はい。意図したとおり、
EMP -> DEPT -> SALGRADE になっていますね。
遅そうですけど。


よく間違う例は、表の実名を書いてしまうパターン。
こんな感じ。

SQL> EXPLAIN PLAN FOR
 2  SELECT /*+ LEADING ( EMP DEPT SALGRADE ) */
 3         E.ENAME ,E.SAL ,D.DNAME ,S.GRADE
 4    FROM EMP E
 5        ,DEPT D
 6        ,SALGRADE S
 7   WHERE E.DEPTNO = D.DEPTNO
 8     AND E.SAL > S.LOSAL
 9     AND E.SAL <= S.HISAL;

解析されました。

SQL> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------
Plan hash value: 721498669

-----------------------------------------------------------------------------------------
| Id  | Operation                    | Name     | Rows  | Bytes | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |          |     1 |    94 |     9  (23)| 00:00:01 |
|   1 |  NESTED LOOPS                |          |     1 |    94 |     9  (23)| 00:00:01 |
|   2 |   MERGE JOIN                 |          |     1 |    72 |     8  (25)| 00:00:01 |
|   3 |    SORT JOIN                 |          |     5 |   195 |     4  (25)| 00:00:01 |
|   4 |     TABLE ACCESS FULL        | SALGRADE |     5 |   195 |     3   (0)| 00:00:01 |
|*  5 |    FILTER                    |          |       |       |            |          |
|*  6 |     SORT JOIN                |          |    12 |   396 |     4  (25)| 00:00:01 |
|   7 |      TABLE ACCESS FULL       | EMP      |    12 |   396 |     3   (0)| 00:00:01 |
|   8 |   TABLE ACCESS BY INDEX ROWID| DEPT     |     1 |    22 |     1   (0)| 00:00:01 |
|*  9 |    INDEX UNIQUE SCAN         | PK_DEPT  |     1 |       |     0   (0)| 00:00:01 |
-----------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

  5 - filter("E"."SAL"<="S"."HISAL")
  6 - access("E"."SAL">"S"."LOSAL")
      filter("E"."SAL">"S"."LOSAL")
  9 - access("E"."DEPTNO"="D"."DEPTNO")

Note
-----
  - dynamic sampling used for this statement

28行が選択されました。


EMP -> SALGRADE -> DEPT で、
一番初めに取った実行計画と同じになってますね。
完璧に無視されてます。


ちなみに、どこまで無視されるかですが、
LEADING に関しては、かっこ内の指定を一つでも間違えたからといって、
LEADINGヒント 全体が無視される訳では無いようです。


たとえば、下記は
DEPT -> SALGRADE -> EMP にしたかったけど、
後ろ二つの指定を間違えた場合。

SQL> EXPLAIN PLAN FOR
 2  SELECT /*+ LEADING ( D SALGRADE EMP ) */
 3         E.ENAME ,E.SAL ,D.DNAME ,S.GRADE
 4    FROM EMP E
 5        ,DEPT D
 6        ,SALGRADE S
 7   WHERE E.DEPTNO = D.DEPTNO
 8     AND E.SAL > S.LOSAL
 9     AND E.SAL <= S.HISAL;

解析されました。

SQL> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------
Plan hash value: 1826023096

---------------------------------------------------------------------------------
| Id  | Operation            | Name     | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------------
|   0 | SELECT STATEMENT     |          |     1 |    94 |    12  (25)| 00:00:01 |
|   1 |  MERGE JOIN          |          |     1 |    94 |    12  (25)| 00:00:01 |
|   2 |   SORT JOIN          |          |    12 |   660 |     8  (25)| 00:00:01 |
|*  3 |    HASH JOIN         |          |    12 |   660 |     7  (15)| 00:00:01 |
|   4 |     TABLE ACCESS FULL| DEPT     |     4 |    88 |     3   (0)| 00:00:01 |
|   5 |     TABLE ACCESS FULL| EMP      |    12 |   396 |     3   (0)| 00:00:01 |
|*  6 |   FILTER             |          |       |       |            |          |
|*  7 |    SORT JOIN         |          |     5 |   195 |     4  (25)| 00:00:01 |
|   8 |     TABLE ACCESS FULL| SALGRADE |     5 |   195 |     3   (0)| 00:00:01 |
---------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

  3 - access("E"."DEPTNO"="D"."DEPTNO")
  6 - filter("E"."SAL"<="S"."HISAL")
  7 - access(INTERNAL_FUNCTION("E"."SAL")>INTERNAL_FUNCTION("S"."LOSAL")
             )
      filter(INTERNAL_FUNCTION("E"."SAL")>INTERNAL_FUNCTION("S"."LOSAL")
             )

Note
-----
  - dynamic sampling used for this statement

29行が選択されました。


正しく記述できている DEPT だけは最初に読まれていますので、
ヒント自体は有効になっているみたいですね。


ということで、
ヒントが有効になっていないように見える場合はこの辺に
注意してみてください。