本シリーズの締めくくりとして、症状・計画パターンごとに「最初に確認するセクション」「疑われる原因」「改善策」を整理したシナリオ集です。困ったときの早見表として使ってください。

1. TABLE ACCESS FULL が意図せず選ばれている

  • 最初に確認: Predicate Information(インデックス列に関数適用・型不一致・NULL 条件がないか)
  • 疑われる原因: インデックスが効かない述語(関数適用・暗黙変換)/統計情報の誤り(行数を少なく見積もっている)/インデックス未作成
  • 改善策: 述語からの関数除去・Function-Based Index 作成・統計情報収集・インデックス追加

2. インデックスが使われているのに遅い

  • 最初に確認: ALLSTATS の Buffers と A-Rows(取得行数 × クラスタリングファクター)
  • 疑われる原因: クラスタリングファクタが高い(インデックス順と物理行順が大きく乖離)/Predicate Information の filter 条件が多くインデックスで絞り込めていない
  • 改善策: テーブルを物理的に整理(再作成・IOT 化)/複合インデックスに追加列を含めて filter を access に昇格

3. NESTED LOOPS が大量ループしている

  • 最初に確認: ALLSTATS の Starts(内側ループの起動回数)/駆動表の A-Rows
  • 疑われる原因: 駆動表の E-Rows が実際より小さく見積もられ NESTED LOOPS が選ばれた(統計情報の誤り)
  • 改善策: 統計情報収集 → 自然に HASH JOIN に切り替わるか確認。強制したい場合は USE_HASH ヒント

4. HASH JOIN で TEMP SPILL が発生している

  • 最初に確認: ALLSTATS の Writes(0 でない場合は SPILL)/AWR の direct path write temp 待機
  • 疑われる原因: Build 側の行数・バイト数が PGA Work Area を超えている
  • 改善策: Build 側の事前フィルタ強化 → データ量削減/PGA サイズ増量(一時)/バッチ処理の分割

5. SORT ORDER BY で全件ソートが発生している

  • 最初に確認: SORT ORDER BY の A-Rows と直前ステップの行数。STOPKEY が付いているか確認
  • 疑われる原因: ROWNUM/OFFSET を外側に置いたページングクエリで ORDER BY が全件ソートになっている
  • 改善策: ORDER BY をインラインビューに移動 + ROWNUM を外側に置く正しいページングパターンに修正。またはソート順と一致するインデックスで INDEX FULL SCAN を利用

6. FILTER 操作でループが発生している

  • 最初に確認: FILTER ノードの上の子(外側行数)と下の子(ループ内サブクエリ)のステップ
  • 疑われる原因: EXISTS / IN サブクエリが SEMI JOIN に変換されず FILTER になっている
  • 改善策: EXISTS を JOIN に書き換え。または統計情報整備で CBO が自動的に HASH JOIN SEMI を選択できるようにする

7. E-Rows と A-Rows が 100 倍以上乖離している

  • 最初に確認: Note(dynamic statistics / SQL Plan Baseline)/DBA_TAB_STATISTICS.LAST_ANALYZED
  • 疑われる原因: 統計情報が古い・ヒストグラムが未収集・AND 条件の相関を考慮していない
  • 改善策: DBMS_STATS.GATHER_TABLE_STATS(ヒストグラムあり)/Extended Statistics の作成

8. MERGE JOIN CARTESIAN(直積)が出現している

  • 最初に確認: Rows 列(急激に増加しているステップ)/SQL の FROM 句と結合条件
  • 疑われる原因: テーブル間の結合条件が書き忘れられている/FROM 句の表数と JOIN 条件数が合っていない
  • 改善策: SQL の結合条件を確認・追記。意図的な CROSS JOIN であれば CROSS JOIN 構文を明示

9. 同じ SQL の計画が毎回変わる

  • 最初に確認: Note(Adaptive Plan / Cardinality Feedback / ACS)/Child Number の数
  • 疑われる原因: バインド変数の値によって行数推定が変わり計画が揺れている(ACS が動作)
  • 改善策: 統計情報整備でどのバインド値でも同一計画が安定して選ばれるようにする。頻繁な計画変化が問題なら SPM で安定した計画をベースラインに登録

10. INDEX SKIP SCAN が選ばれている

  • 最初に確認: 複合インデックスの先頭列のカーディナリティ(DBA_TAB_COL_STATISTICS.NUM_DISTINCT)
  • 疑われる原因: 先頭列のカーディナリティが実際は高いのに統計情報が古く低く見積もられている
  • 改善策: 統計情報再収集 → 後続列単独インデックスの作成か先頭列を条件に追加

11. COUNT(*) が遅い

  • 最初に確認: Plan で INDEX FAST FULL SCAN または TABLE ACCESS FULL のどちらが選ばれているか
  • 疑われる原因: インデックスが存在するのに FULL TABLE SCAN になっている(統計情報の誤り)
  • 改善策: 最小カラムの NOT NULL インデックスを作成 → INDEX FAST FULL SCAN を誘導

12. スカラーサブクエリが繰り返し実行されている

  • 最初に確認: SQL の SELECT 句にサブクエリがあるか。ALLSTATS で Plan の Buffers が外側 Rows に比例して増えていないか
  • 疑われる原因: スカラーサブクエリのキャッシュが効いていない(引数カーディナリティが高い)
  • 改善策: FROM 句のインラインビュー + HASH JOIN に書き換え。または SCALAR_SUBQUERY_UNNEST ヒント

13. Note に "dynamic statistics used" が頻出する

  • 最初に確認: DBA_TAB_STATISTICS で対象表の LAST_ANALYZED が NULL または極めて古い
  • 疑われる原因: 統計情報が一度も収集されていない・TRUNCATE 後に再収集されていない・本番への移行時に統計情報が移されていない
  • 改善策: DBMS_STATS で統計情報を収集。定期的な自動統計収集ジョブの設定・確認

14. CONNECT BY が非常に遅い

  • 最初に確認: 階層の深さ(MAX LEVEL)と各レベルの行数(A-Rows の推移)
  • 疑われる原因: 階層が深い・ファンアウトが大きい・WHERE 条件が CONNECT BY より外側にある(結果取得後にフィルタ)
  • 改善策: WHERE 条件を CONNECT BY ... AND 条件 の形に押し込む/再帰 WITH 句に書き換えて最適化の余地を増やす

15. SQL Plan Baseline 使用後に急に遅くなった

  • 最初に確認: Note に SQL Plan Baseline が表示されているか確認。DBA_SQL_PLAN_BASELINES で ACCEPTED/ENABLED 状態を確認
  • 疑われる原因: テーブルのデータ量変化・統計情報更新後も古い計画がベースラインに固定されている
  • 改善策: 新しい計画を DBMS_SPM.EVOLVE_SQL_PLAN_BASELINE で評価 → より良い計画として ACCEPT に更新。古いベースラインは DBMS_SPM.DROP_SQL_PLAN_BASELINE で削除