「インデックスを使ってほしいのにフルスキャンになる」「JOINの順序を変えたい」——Oracleのオプティマイザの判断を上書きするのがヒント句です。本記事では現場でよく使うヒント句を実例付きで解説します。
目次
1. ヒント句とは(いつ使うか) {#about}
ヒント句とはSQL文中に埋め込む特殊なコメントで、オプティマイザの実行計画を指定の方向に誘導します。
使う場面:
- 統計情報が古くてオプティマイザが誤った判断をしている
- インデックスがあるのに使われない
- 大量データの結合でNested Loopが選ばれて遅い
- バッチ処理でフルスキャンの方が速いのにインデックスを使っている
原則:ヒント句は最後の手段。まず統計情報の更新・インデックス見直し・SQLのリライトで対処し、それでも改善しない場合にヒント句を使う。
2. ヒント句の書き方の基本 {#syntax}
-- ヒント句の書式:SELECT の直後に /*+ ヒント名 */ を記述
SELECT /*+ INDEX(e IDX_EMP_DEPT_ID) */
e.employee_id, e.last_name
FROM employees e
WHERE e.department_id = 10;
-- 複数のヒント句を同時に指定できる(スペース区切り)
SELECT /*+ FULL(e) PARALLEL(e, 4) */
COUNT(*)
FROM employees e;注意:
/*+と*/の間に記述する(通常のコメントと混同しない)- ヒント句内の構文エラーはOracleが無視する(エラーにならない)
- ヒント句はSQL文の最初のキーワードの直後にのみ書ける
3. アクセスパスのヒント句 {#access-hints}
INDEX:インデックスを使う
-- 特定のインデックスを使わせる
SELECT /*+ INDEX(e IDX_EMP_DEPT_ID) */
employee_id, last_name
FROM employees e
WHERE department_id = 10;
-- どのインデックスかをオプティマイザに選ばせる(インデックスは使う)
SELECT /*+ INDEX(e) */
employee_id, last_name
FROM employees e
WHERE department_id = 10;NO_INDEX:インデックスを使わない
-- インデックスを使わせない(フルスキャンを強制)
SELECT /*+ NO_INDEX(e IDX_EMP_DEPT_ID) */
employee_id, last_name
FROM employees e
WHERE department_id = 10;FULL:フルスキャンを強制
-- フルテーブルスキャンを強制
SELECT /*+ FULL(e) */
COUNT(*)
FROM employees e;バッチ処理でテーブルの大部分を読む場合、インデックスを使うよりフルスキャンの方が速いことがある。
INDEX_FFS:高速フルスキャン(インデックスのみ読む)
-- インデックスのみを読んでテーブルを読まない(カバリングインデックス前提)
SELECT /*+ INDEX_FFS(e IDX_EMP_DEPT_ID) */
COUNT(department_id)
FROM employees e;4. 結合方法のヒント句 {#join-hints}
USE_NL:Nested Loop Joinを使う
-- 小さいテーブル×インデックスのある大きいテーブルに有効
SELECT /*+ USE_NL(e d) */
e.last_name, d.department_name
FROM employees e
JOIN departments d ON e.department_id = d.department_id
WHERE e.hire_date >= DATE '2024-01-01';USE_HASH:Hash Joinを使う
-- 大量データ同士の結合に有効
SELECT /*+ USE_HASH(e d) */
e.last_name, d.department_name
FROM employees e
JOIN departments d ON e.department_id = d.department_id;USE_MERGE:Sort Merge Joinを使う
-- ソート済みデータや範囲条件の結合に有効
SELECT /*+ USE_MERGE(e d) */
e.last_name, d.department_name
FROM employees e
JOIN departments d ON e.department_id = d.department_id;LEADING:結合の駆動表(先頭テーブル)を指定
-- employees を駆動表にする(先に読んでからdepartmentsと結合)
SELECT /*+ LEADING(e d) USE_NL(d) */
e.last_name, d.department_name
FROM employees e
JOIN departments d ON e.department_id = d.department_id;5. 並列処理のヒント句 {#parallel-hints}
PARALLEL:並列クエリを有効化
-- 4並列でテーブルをスキャン
SELECT /*+ PARALLEL(sales, 4) */
SUM(amount)
FROM sales;
-- 自動並列度(オプティマイザが決める)
SELECT /*+ PARALLEL(sales, AUTO) */
SUM(amount)
FROM sales;NO_PARALLEL:並列処理を抑制
-- 並列処理を無効化(OLTP系の単発クエリ向け)
SELECT /*+ NO_PARALLEL(e) */
employee_id, last_name
FROM employees e
WHERE employee_id = 100;APPEND:ダイレクトパスINSERT
-- ログを最小化して高速INSERT(バッファキャッシュを経由しない)
INSERT /*+ APPEND */ INTO sales_backup
SELECT * FROM sales WHERE sale_date < DATE '2024-01-01';
COMMIT;APPEND ヒントを使った後はCOMMITするまでそのテーブルへのSELECTができない点に注意。
6. その他よく使うヒント句 {#other-hints}
RESULT_CACHE:クエリ結果をキャッシュ
-- 同じSQLの結果をメモリにキャッシュ(参照系の繰り返し実行に有効)
SELECT /*+ RESULT_CACHE */
department_id, COUNT(*) AS 人数
FROM employees
GROUP BY department_id;MATERIALIZE / INLINE:WITH句(CTE)の制御
-- WITH句の結果を一時テーブルとして具体化する(大きなCTEに有効)
WITH dept_summary AS (
/*+ MATERIALIZE */
SELECT department_id, AVG(salary) AS avg_sal
FROM employees
GROUP BY department_id
)
SELECT * FROM dept_summary WHERE avg_sal > 50000;
-- WITH句をインライン展開する(小さいCTEに有効)
WITH dept_summary AS (
/*+ INLINE */
SELECT department_id, AVG(salary) AS avg_sal
FROM employees
GROUP BY department_id
)
SELECT * FROM dept_summary WHERE avg_sal > 50000;FIRST_ROWS:最初のN行を速く返す
-- 最初の10行を優先して返す(画面表示系のページング)
SELECT /*+ FIRST_ROWS(10) */
employee_id, last_name
FROM employees
ORDER BY hire_date DESC;ALL_ROWS:全行を効率よく返す(デフォルト)
-- バッチ処理など全行取得が前提の場合
SELECT /*+ ALL_ROWS */
employee_id, last_name, salary
FROM employees;7. ヒント句が効かないときの確認方法 {#debug}
-- EXPLAIN PLANで実行計画を確認
EXPLAIN PLAN FOR
SELECT /*+ INDEX(e IDX_EMP_DEPT_ID) */
employee_id, last_name
FROM employees e
WHERE department_id = 10;
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY(NULL, NULL, 'ALL'));Note欄を確認:
Plan hash value: XXXXXXXX
Note
-----
- hints were ignored because ...ヒントが無視される場合は Note 欄にその理由が表示されます。
ヒントが無視される主な原因:
| 原因 | 対処 |
|---|---|
| テーブル別名とヒントが不一致 | 別名を使っている場合はヒントにも別名を使う |
| インデックス名が間違っている | USER_INDEXES でインデックス名を確認 |
| 文法ミス(スペースなど) | /*+ の後はスペースを入れる |
| オプティマイザが別のプランを選択 | DBMS_XPLAN.DISPLAY の Note を確認 |
8. まとめ:現場で使うヒント句早見表 {#summary}
| 目的 | ヒント句 | 例 |
|---|---|---|
| インデックスを使う | INDEX(表名 インデックス名) | /*+ INDEX(e IDX_EMP_DEPT) */ |
| フルスキャンを使う | FULL(表名) | /*+ FULL(e) */ |
| Nested Loopで結合 | USE_NL(表名) | /*+ USE_NL(e d) */ |
| Hash Joinで結合 | USE_HASH(表名) | /*+ USE_HASH(e d) */ |
| 駆動表を指定 | LEADING(表名...) | /*+ LEADING(e d) */ |
| 並列処理 | PARALLEL(表名, 度数) | /*+ PARALLEL(s, 4) */ |
| 高速INSERT | APPEND | INSERT /*+ APPEND */ |
| 結果キャッシュ | RESULT_CACHE | /*+ RESULT_CACHE */ |
関連記事:









コメント