【Oracle】DBMS_XPLAN完全ガイド|DISPLAY・DISPLAY_CURSOR・DISPLAY_AWR・実行計画の読み方まで解説

【Oracle】DBMS_XPLAN完全ガイド|DISPLAY・DISPLAY_CURSOR・DISPLAY_AWR・実行計画の読み方まで解説 Oracle

Oracle のパフォーマンスチューニングで「実行計画を確認する」という作業は日常的ですが、EXPLAIN PLAN + AUTOTRACE だけでは把握できない情報があります。実際に実行されたプランの統計・AWR に蓄積されたプラン履歴・バインド変数を使ったときの実際のカーディナリティ推定を確認するには、DBMS_XPLAN パッケージが必要です。

この記事では DBMS_XPLAN の3つの主要関数(DISPLAY・DISPLAY_CURSOR・DISPLAY_AWR)の使い方と、出力の読み方を実例で解説します。

この記事でわかること

  • DBMS_XPLAN.DISPLAY・DISPLAY_CURSOR・DISPLAY_AWR の違いと使い分け
  • FORMAT パラメータ(BASIC/TYPICAL/ALL/ALLSTATS LAST)の使い方
  • 出力の読み方:Operation・Cost・Card・Bytes・Time の意味
  • アクセス述語(Access Predicates)とフィルタ述語(Filter Predicates)の違い
  • SQL_ID を使って特定クエリのプランをピンポイントで確認する方法
  • バインドピーク(Bind Peeking)の問題と実際の実行統計の取得方法
スポンサーリンク

DBMS_XPLAN の3つの主要関数

関数 参照先 用途
DISPLAY PLAN_TABLE(EXPLAIN PLAN の結果) 事前に EXPLAIN PLAN を実行したプランを整形表示する
DISPLAY_CURSOR V$SQL_PLAN / V$SQL_PLAN_STATISTICS 現在または直近に実行された SQL の実際のプラン(実行統計付き)を確認する
DISPLAY_AWR DBA_HIST_SQL_PLAN AWR スナップショットに蓄積された過去のプランを確認する(DBA権限必要)
EXPLAIN PLAN のプランと実際の実行プランは異なる場合がある
EXPLAIN PLAN は「現在のセッション設定・統計情報」でのプランを予測します。バインド変数を使うクエリでは、実際に実行されたときのバインドピーク(Bind Peeking)の結果と異なる場合があります。実際に使われたプランを確認したいときは DISPLAY_CURSOR を使います。

DBMS_XPLAN.DISPLAY — EXPLAIN PLAN の結果を整形表示する

EXPLAIN PLAN 実行 → DISPLAY で整形表示
-- ① EXPLAIN PLAN でプランを PLAN_TABLE に格納
EXPLAIN PLAN FOR
SELECT e.employee_id, e.last_name, d.department_name
FROM employees e
JOIN departments d ON e.department_id = d.department_id
WHERE e.salary > 10000
ORDER BY e.last_name;

-- ② DBMS_XPLAN.DISPLAY で整形表示
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);
-- デフォルト: statement_id=NULL(直近の EXPLAIN PLAN), format='TYPICAL'
FORMAT パラメータの指定例
-- BASIC: 最小限(Operation と Name のみ)
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY(format=>'BASIC'));

-- TYPICAL: 標準(デフォルト)Operation, Cost, Card, Bytes, Time, Predicate 情報
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY(format=>'TYPICAL'));

-- ALL: 最詳細(Column Projection, Alias なども含む)
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY(format=>'ALL'));

-- OUTLINE: SQL ヒントを再現するための Outline Data を表示
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY(format=>'TYPICAL +OUTLINE'));

複数の EXPLAIN PLAN を区別したいときは SET STATEMENT_ID = '識別名' を使います。

STATEMENT_ID で複数プランを管理する
-- プランに識別名を付けて保存
EXPLAIN PLAN SET STATEMENT_ID = 'PLAN_A' FOR
SELECT * FROM employees WHERE department_id = 50;

EXPLAIN PLAN SET STATEMENT_ID = 'PLAN_B' FOR
SELECT * FROM employees WHERE salary > 5000;

-- 識別名を指定して表示
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY('PLAN_TABLE', 'PLAN_A', 'TYPICAL'));
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY('PLAN_TABLE', 'PLAN_B', 'TYPICAL'));

実行計画の出力の読み方

DBMS_XPLAN の出力にはいくつかの重要な列があります。以下に主要な列の意味を示します。

列名 意味 チューニングでの注目点
Id 操作のID(*付きは述語情報あり) 述語情報(Predicate Information)と照合する
Operation 実行した操作(TABLE ACCESS FULL など) 意図しない FULL SCAN がないか確認
Name 操作対象のオブジェクト名 想定通りのインデックスが使われているか
Rows (Card) オプティマイザの推定行数 実際の行数と大きく乖離していると統計情報が古い
Bytes 推定データ量 ソート・ハッシュ結合での一時領域使用量の目安
Cost オプティマイザの内部コスト(I/O + CPU) コストが高い操作がボトルネック候補
Time 推定処理時間(hh:mm:ss) 参考値(実際とは異なる場合が多い)

アクセス述語とフィルタ述語の違い

実行計画の下部に表示される「Predicate Information」は、WHERE 条件がどの段階で適用されるかを示します。

種類 意味 パフォーマンスへの影響
access(“col”=:b1) インデックスを使ってデータにアクセスする条件 インデックスが有効活用されている(良い)
filter(“col”>100) 読み込んだ後に行を絞り込む条件 余分な読み込みが発生している可能性(要確認)
filter が大量に出る場合は要注意
インデックスの先頭列に指定していない条件や、関数を適用した列への条件は、access でなく filter になります。これは「インデックスは使っているが効率が悪い」状態を示すことがあります。関数ベースインデックス(FBI)の検討が有効です。

DBMS_XPLAN.DISPLAY_CURSOR — 実際の実行プランを確認する

DISPLAY_CURSOR は V$SQL_PLAN(プラン情報)と V$SQL_PLAN_STATISTICS(実行統計)から実際に使われたプランを取得します。EXPLAIN PLAN では取れない実際のカーディナリティや実行時間を確認できます。

直前に実行した SQL のプランを確認する
-- ① 対象の SQL を実行(統計収集を有効にするためヒントを付ける)
SELECT /*+ GATHER_PLAN_STATISTICS */
    e.employee_id, e.last_name, d.department_name
FROM employees e
JOIN departments d ON e.department_id = d.department_id
WHERE e.salary > 10000;

-- ② 直前に実行した SQL のプランを確認(sql_id=NULL → 直前の SQL)
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(format=>'ALLSTATS LAST'));
SQL_ID を指定してプランを確認する
-- ① 対象の SQL の sql_id を取得
SELECT sql_id, sql_text, executions
FROM V$SQL
WHERE sql_text LIKE '%employees%'
  AND sql_text NOT LIKE '%V$SQL%'
ORDER BY last_active_time DESC
FETCH FIRST 5 ROWS ONLY;

-- ② sql_id と child_number を指定してプランを表示
SELECT * FROM TABLE(
    DBMS_XPLAN.DISPLAY_CURSOR(
        sql_id       => 'abc123xyz',  -- V$SQL の sql_id を指定
        cursor_child_no => 0,         -- 通常は 0
        format       => 'ALLSTATS LAST'
    )
);

ALLSTATS LAST の読み方

format=>'ALLSTATS LAST' を指定すると、推定値(E-Rows)と実際の値(A-Rows)が並んで表示されます。

意味
E-Rows オプティマイザの推定行数(Estimated Rows)
A-Rows 実際に処理した行数(Actual Rows)
A-Time 実際の処理時間(累積)
Buffers 実際に読み込んだバッファブロック数(論理読み込み)
Reads 物理読み込みブロック数
E-Rows と A-Rows の大きな乖離はプラン誤りのサイン
例えば「E-Rows=1, A-Rows=50000」という状態は、オプティマイザが「1行しか返らない」と誤って判断してNESTED LOOPS を選んだが、実際には 50,000 行を処理していた、というケースを示します。この場合はSQL ヒントや統計情報の更新(DBMS_STATS)で対処します。

DISPLAY_CURSOR を使うには SELECT_CATALOG_ROLE または V$SQL_PLAN への SELECT 権限が必要です。一般ユーザーが自分のセッションで実行した SQL については通常参照できます。

GATHER_PLAN_STATISTICS ヒントなしで実行統計を取得する方法
-- セッション全体で実行統計収集を有効にする(パフォーマンスに影響するため一時的に使用)
ALTER SESSION SET statistics_level = ALL;

-- 対象 SQL を実行
SELECT e.employee_id, e.last_name
FROM employees e
WHERE e.department_id = 50;

-- プランと実行統計を確認
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(format=>'ALLSTATS LAST'));

-- 確認後は設定を戻す
ALTER SESSION SET statistics_level = TYPICAL;

DBMS_XPLAN.DISPLAY_AWR — AWR からプランを取得する

DISPLAY_AWR は AWR(Automatic Workload Repository)に蓄積されたプランを参照します。「昨日は遅くなかったのに今日は遅い」という場合に、過去のプランと現在のプランを比較するのに使います。DBA 権限または SELECT ANY DICTIONARY 権限が必要です。

AWR から sql_id のプランを確認する(DBA権限必要)
-- ① 対象 SQL の sql_id を DBA_HIST_SQLSTAT から確認
SELECT DISTINCT sql_id, plan_hash_value, snap_id
FROM DBA_HIST_SQLSTAT
WHERE sql_id = 'abc123xyz'
ORDER BY snap_id DESC
FETCH FIRST 10 ROWS ONLY;

-- ② AWR からプランを取得(plan_hash_value を指定して特定バージョンを参照)
SELECT * FROM TABLE(
    DBMS_XPLAN.DISPLAY_AWR(
        sql_id        => 'abc123xyz',
        plan_hash_value => 1234567890,  -- NULL なら全バージョン表示
        db_id         => NULL,          -- NULL = 現在の DB
        format        => 'TYPICAL'
    )
);
プラン変更の前後を比較する
-- AWR に蓄積されている plan_hash_value の種類を確認
-- plan_hash_value が変わっている = プランが切り替わったタイミング
SELECT
    s.snap_id,
    TO_CHAR(sn.begin_interval_time, 'YYYY-MM-DD HH24:MI') AS snap_time,
    s.plan_hash_value,
    s.executions_delta,
    ROUND(s.elapsed_time_delta / NULLIF(s.executions_delta, 0) / 1000000, 2) AS avg_elapsed_sec
FROM DBA_HIST_SQLSTAT s
JOIN DBA_HIST_SNAPSHOT sn ON s.snap_id = sn.snap_id
WHERE s.sql_id = 'abc123xyz'
ORDER BY s.snap_id;

FORMAT パラメータのまとめ

FORMAT 値 表示内容 主な用途
BASIC Operation と Name のみ 全体構造だけ確認したい場合
TYPICAL(デフォルト) 標準情報(Cost・Card・Bytes・Time・述語) 通常のプラン確認
ALL TYPICAL + 列プロジェクション・エイリアス情報 詳細分析
ALLSTATS LAST TYPICAL + E-Rows/A-Rows/A-Time/Buffers(最終実行分) 実際の実行統計との比較(DISPLAY_CURSOR 専用)
ALLSTATS 全実行の統計(累積) ALLSTATS LAST より多くのデータ
TYPICAL +OUTLINE TYPICAL + Outline Data(SQL プロファイル用ヒント) プランを固定するヒントを取得したい場合

よく使う実践パターン

現在実行中のセッションの実行プランを確認する

実行中セッションのプランを確認する
-- ① 現在実行中のセッションの sql_id を確認
SELECT s.sid, s.sql_id, s.sql_child_number, s.event, s.seconds_in_wait
FROM V$SESSION s
WHERE s.status = 'ACTIVE'
  AND s.wait_class != 'Idle'
ORDER BY s.seconds_in_wait DESC;

-- ② 該当セッションのプランを確認
SELECT * FROM TABLE(
    DBMS_XPLAN.DISPLAY_CURSOR(
        sql_id          => 'abc123xyz',   -- ①で取得した sql_id
        cursor_child_no => 0,
        format          => 'TYPICAL'
    )
);

プランが意図通りかを素早く確認するワンライナーパターン

EXPLAIN PLAN + DISPLAY を一発で確認する
-- EXPLAIN PLAN と DISPLAY を連続実行(SQL*Plus / SQLcl で使用)
EXPLAIN PLAN FOR
SELECT COUNT(*) FROM orders WHERE order_date >= DATE '2024-01-01';

SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY(format=>'TYPICAL'));

バインド変数を使ったクエリのプランを確認する

バインドピーク確認のパターン
-- バインド変数を使ったクエリを実行
VARIABLE b_dept NUMBER;
EXEC :b_dept := 50;

SELECT /*+ GATHER_PLAN_STATISTICS */ *
FROM employees
WHERE department_id = :b_dept;

-- 実際に使われたプランを確認(バインドピークされた値でのプラン)
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(format=>'ALLSTATS LAST +PEEKED_BINDS'));
-- +PEEKED_BINDS: バインドピークした値を出力に含める(Oracle 11g 以降)

まとめ

  • DISPLAY:EXPLAIN PLAN の結果を整形表示する。簡単に使えるが実際の実行とは異なる場合がある
  • DISPLAY_CURSOR:実際に実行されたプランと実行統計を確認できる。format=>'ALLSTATS LAST' で E-Rows vs A-Rows の乖離を発見できる
  • DISPLAY_AWR:過去のプランを確認する。プランが変わってから遅くなった場合に原因特定できる
  • 述語の読み方access はインデックスが効いている条件、filter は読み込み後に絞り込む条件。filter の多用は非効率の兆候
  • ALLSTATS LAST:推定(E-Rows)と実際(A-Rows)の乖離が大きければ統計情報の更新やヒントで対処する

実行計画の確認は EXPLAIN PLAN・AUTOTRACE完全ガイド と合わせて使うと効果的です。遅い SQL の特定には V$SQL・AWRで遅いSQLを特定する方法 も参照してください。プランをヒントで固定したい場合は SQL ヒント句完全ガイドを参照してください。