【Oracle】遅い SQL を特定する方法完全ガイド|V$SQL・V$SESSION・AWR・ASH・ADDM・Statspack まで解説

【Oracle】遅い SQL を特定する方法完全ガイド|V$SQL・V$SESSION・AWR・ASH・ADDM・Statspack まで解説 Oracle

Oracle データベースでパフォーマンス問題が発生したとき、最初に行うべきは「どの SQL が遅いのか」を特定することです。闇雲にインデックスを追加したりパラメータを変更するのではなく、ボトルネックの SQL を正確に見つけてから対処することで効率的にチューニングできます。

本記事では、V$SQL でのTop N 分析V$SESSION で現在実行中の重い SQL をリアルタイム特定AWR / ASH / ADDM での履歴分析、そしてStandard Edition 向けの Statspack まで、段階的なアプローチで解説します。

この記事でわかること
・V$SQL で経過時間 / CPU / I/O の Top N SQL を特定するクエリ
・V$SESSION で現在実行中の重い SQL をリアルタイムで特定する方法
・V$SQL_MONITOR で長時間 SQL の進捗を確認する方法
・AWR レポートの読み方(SQL ordered by Elapsed Time)
・ASH(Active Session History)で待機イベントを分析する方法
・ADDM の自動推奨レポート
・Standard Edition 向けの Statspack
・段階的アプローチ(今 → 過去 → 定期)
スポンサーリンク

遅い SQL 特定の段階的アプローチ

段階 状況 使うツール 対象
(1) リアルタイム 今まさにシステムが遅い V$SESSION / V$SQL_MONITOR 現在実行中の SQL
(2) 直近の履歴 「さっき遅かった」SQL を特定したい V$SQL / V$ACTIVE_SESSION_HISTORY 共有プール内の SQL 統計
(3) 過去の履歴 昨日 / 先週の遅い SQL を調べたい AWR / ASH レポート スナップショット間の統計
(4) 定期分析 継続的にパフォーマンスを監視 ADDM / Statspack 自動推奨 / 定期レポート

V$SQL で Top N SQL を特定する

V$SQL は Oracle の共有プール内の全 SQL 統計を保持するビューです。経過時間・CPU 時間・I/O 回数・実行回数などでソートして「最もリソースを消費している SQL」を特定します。

経過時間(Elapsed Time)の Top 10

SQL(総経過時間が長い SQL Top 10)
SELECT * FROM (
    SELECT
        sql_id,
        ROUND(elapsed_time / 1000000, 2) AS elapsed_sec,
        executions,
        ROUND(elapsed_time / NULLIF(executions, 0) / 1000000, 2) AS avg_sec,
        ROUND(cpu_time / 1000000, 2) AS cpu_sec,
        disk_reads,
        buffer_gets,
        SUBSTR(sql_text, 1, 100) AS sql_text_short
    FROM v$sql
    ORDER BY elapsed_time DESC
)
WHERE ROWNUM <= 10;

CPU 時間の Top 10

SQL(CPU 消費が多い SQL Top 10)
SELECT * FROM (
    SELECT sql_id,
           ROUND(cpu_time/1000000, 2) AS cpu_sec,
           executions,
           ROUND(cpu_time/NULLIF(executions,0)/1000000, 2) AS avg_cpu_sec,
           SUBSTR(sql_text, 1, 100) AS sql_text_short
    FROM v$sql
    ORDER BY cpu_time DESC
)
WHERE ROWNUM <= 10;

ディスク読み取り(I/O)の Top 10

SQL(ディスク I/O が多い SQL Top 10)
SELECT * FROM (
    SELECT sql_id,
           disk_reads,
           executions,
           ROUND(disk_reads/NULLIF(executions,0)) AS avg_disk_reads,
           buffer_gets,
           SUBSTR(sql_text, 1, 100) AS sql_text_short
    FROM v$sql
    ORDER BY disk_reads DESC
)
WHERE ROWNUM <= 10;

1 回あたりの実行時間が長い SQL

SQL(平均実行時間が長い SQL Top 10)
SELECT * FROM (
    SELECT sql_id,
           executions,
           ROUND(elapsed_time/NULLIF(executions,0)/1000000, 2) AS avg_elapsed_sec,
           ROUND(cpu_time/NULLIF(executions,0)/1000000, 2) AS avg_cpu_sec,
           ROUND(buffer_gets/NULLIF(executions,0)) AS avg_buffer_gets,
           SUBSTR(sql_text, 1, 100) AS sql_text_short
    FROM v$sql
    WHERE executions > 0
    ORDER BY elapsed_time/executions DESC
)
WHERE ROWNUM <= 10;

V$SQL の主要列

単位 説明
sql_id SQL の一意識別子(EXPLAIN PLAN や AWR で使用)
elapsed_time マイクロ秒 総経過時間(待機含む)
cpu_time マイクロ秒 CPU 使用時間
executions 実行回数
disk_reads ブロック 物理ディスク読み取りブロック数
buffer_gets ブロック 論理読み取り(バッファキャッシュ)ブロック数
rows_processed 処理された行数
sql_text SQL テキスト(先頭 1000 文字。全文は V$SQLTEXT)
SQL(sql_id から完全な SQL テキストを取得)
-- V$SQL の sql_text は先頭 1000 文字のみ
-- 完全なテキストは V$SQLTEXT で取得
SELECT sql_text
FROM v$sqltext
WHERE sql_id = 'abc123def456'
ORDER BY piece;

-- または V$SQL_FULLTEXT(CLOB)
SELECT sql_fulltext FROM v$sql WHERE sql_id = 'abc123def456';

V$SESSION で現在実行中の重い SQL を特定する

「今まさにシステムが遅い」というときは、V$SESSION で現在アクティブなセッションと実行中の SQL を確認します。

SQL(現在実行中の SQL を確認)
-- アクティブセッションと実行中 SQL の一覧
SELECT
    s.sid, s.serial#,
    s.username,
    s.status,
    s.event,
    s.wait_class,
    s.seconds_in_wait,
    s.sql_id,
    SUBSTR(q.sql_text, 1, 80) AS sql_text_short
FROM v$session s
LEFT JOIN v$sql q ON s.sql_id = q.sql_id AND s.sql_child_number = q.child_number
WHERE s.status = 'ACTIVE'
  AND s.type = 'USER'
ORDER BY s.seconds_in_wait DESC;
SQL(特定ユーザーの実行中 SQL)
-- アプリケーションユーザーの実行中 SQL に絞る
SELECT s.sid, s.sql_id, s.event,
       s.seconds_in_wait AS wait_sec,
       q.elapsed_time/1000000 AS total_elapsed_sec,
       SUBSTR(q.sql_text, 1, 100) AS sql_text
FROM v$session s
JOIN v$sql q ON s.sql_id = q.sql_id
WHERE s.username = 'APP_USER'
  AND s.status = 'ACTIVE';
wait_class と event で「何を待っているか」がわかる
User I/O: ディスク読み書き待ち(インデックス不足の可能性)
Concurrency: ロック / ラッチ競合
Network: DB Link / クライアント応答待ち
CPU: CPU 割り当て待ち(CPU リソース不足)

V$SQL_MONITOR で長時間 SQL の進捗を確認する

SQL(SQL Monitor: 長時間実行中の SQL)
-- 5 秒以上実行中または並列実行の SQL が自動記録される
SELECT
    sql_id,
    status,
    username,
    ROUND(elapsed_time/1000000, 1) AS elapsed_sec,
    ROUND(cpu_time/1000000, 1) AS cpu_sec,
    buffer_gets,
    disk_reads,
    sql_text
FROM v$sql_monitor
WHERE status = 'EXECUTING'
ORDER BY elapsed_time DESC;
SQL(SQL Monitor レポート: 詳細な進捗)
-- 特定 SQL の詳細レポート(HTML 形式)
SELECT DBMS_SQLTUNE.REPORT_SQL_MONITOR(
    sql_id => 'abc123def456',
    type   => 'TEXT'
) AS report
FROM DUAL;
-- 実行計画の各ステップの進捗率や待機時間が確認できる
V$SQL_MONITOR は Enterprise Edition + Diagnostics Pack のライセンスが必要
V$SQL_MONITOR と DBMS_SQLTUNE.REPORT_SQL_MONITOR はOracle Tuning Pack のライセンスに含まれます。Standard Edition では利用できません。

AWR レポートで過去の遅い SQL を特定する

AWR(Automatic Workload Repository)は、Oracle が 1 時間ごとに自動取得するパフォーマンススナップショットです。「昨日の午後 3 時頃に遅かった」というケースで、その時間帯の Top SQL を特定できます。

Shell(AWR レポートの生成)
-- SQL*Plus で AWR レポートを生成
SQL> @$ORACLE_HOME/rdbms/admin/awrrpt.sql

-- 対話形式で以下を入力:
-- レポート形式: html(推奨)
-- 日数: 1(直近 1 日のスナップショット一覧)
-- 開始スナップショット ID: 100
-- 終了スナップショット ID: 102(例: 2 時間分)
-- レポートファイル名: awr_report.html

AWR レポートの読み方: SQL セクション

セクション名 内容 何がわかるか
SQL ordered by Elapsed Time 総経過時間の Top SQL 最も影響の大きい SQL(最初に見るべき)
SQL ordered by CPU Time CPU 消費の Top SQL CPU ボトルネックの SQL
SQL ordered by Gets バッファ読み取りの Top SQL メモリ消費の多い SQL(インデックス不足の候補)
SQL ordered by Reads ディスク読み取りの Top SQL I/O ボトルネックの SQL
SQL ordered by Executions 実行回数の Top SQL 頻繁に呼ばれる SQL(1 回は軽くても累計で影響大)
AWR レポートの見方のコツ
(1) まず「SQL ordered by Elapsed Time」を見る(全体影響が最大の SQL)
(2) 次に「%Total DB Time」列を確認(DB 全体の何 % を占めるか)
(3) sql_id をメモして V$SQL や EXPLAIN PLAN で詳細分析
上位 5 件を改善するだけで全体パフォーマンスが大幅に向上することが多いです。

ASH(Active Session History)で待機イベントを分析する

SQL(ASH: 直近 1 時間の Top SQL)
-- 直近 1 時間で最も待機時間が長い SQL
SELECT sql_id,
       COUNT(*) AS sample_count,
       ROUND(COUNT(*) * 100.0 / SUM(COUNT(*)) OVER(), 1) AS pct,
       MAX(event) AS top_event
FROM v$active_session_history
WHERE sample_time >= SYSDATE - INTERVAL '1' HOUR
  AND sql_id IS NOT NULL
GROUP BY sql_id
ORDER BY sample_count DESC
FETCH FIRST 10 ROWS ONLY;
SQL(ASH: 特定 SQL の待機イベント内訳)
-- 特定の sql_id で何を待っていたかを分析
SELECT event, wait_class,
       COUNT(*) AS samples,
       ROUND(COUNT(*) * 100.0 / SUM(COUNT(*)) OVER(), 1) AS pct
FROM v$active_session_history
WHERE sql_id = 'abc123def456'
  AND sample_time >= SYSDATE - INTERVAL '1' HOUR
GROUP BY event, wait_class
ORDER BY samples DESC;
Shell(ASH レポートの生成)
-- 特定時間帯の ASH レポートを生成
SQL> @$ORACLE_HOME/rdbms/admin/ashrpt.sql
-- 開始時刻と終了時刻を指定 → Top SQL / Top Events が出力される

ADDM で自動推奨を取得する

ADDM(Automatic Database Diagnostic Monitor)は、AWR スナップショットを分析し、パフォーマンス改善の推奨事項を自動生成します。「何が問題か」「どう直すべきか」を Oracle が教えてくれます。

Shell(ADDM レポートの生成)
-- ADDM レポートを生成
SQL> @$ORACLE_HOME/rdbms/admin/addmrpt.sql
-- スナップショット範囲を指定すると、推奨事項付きレポートが生成される

-- 結果例:
-- FINDING 1: SQL ID "abc123" consumed 45% of DB Time
-- RECOMMENDATION: Consider creating an index on ORDERS(CUSTOMER_ID)

Statspack(Standard Edition 向け)

AWR / ASH / ADDM は Enterprise Edition + Diagnostics Pack のライセンスが必要です。Standard Edition では代わりに Statspack を使います。Statspack は無料で利用可能で、AWR と同様のスナップショットベースの分析ができます。

SQL(Statspack のセットアップと実行)
-- (1) Statspack のインストール(初回のみ)
-- SQL> @$ORACLE_HOME/rdbms/admin/spcreate.sql

-- (2) スナップショットの手動取得
EXEC STATSPACK.SNAP;

-- (3) レポートの生成
SQL> @$ORACLE_HOME/rdbms/admin/spreport.sql
-- 開始/終了のスナップショット ID を指定

-- (4) 自動スナップショット(DBMS_JOB で 1 時間ごと)
-- SQL> @$ORACLE_HOME/rdbms/admin/spauto.sql
機能 AWR(EE + Diagnostics Pack) Statspack(全エディション無料)
スナップショット 自動(1 時間ごと) 手動 or DBMS_JOB で自動化
レポート @awrrpt.sql @spreport.sql
Top SQL
待機イベント分析 ○(ASH) 限定的
自動推奨 ○(ADDM) ×
SQL Monitor ×
ライセンス 有償 無料

遅い SQL を特定した後の次のステップ

特定した内容 次のアクション
sql_id を特定した 実行計画を確認: SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR('sql_id'))
フルテーブルスキャンが原因 適切なインデックスの作成を検討
実行計画は良いが遅い 統計情報の更新: DBMS_STATS.GATHER_TABLE_STATS
ロック / 待機が原因 V$LOCK / V$SESSION で競合セッションを特定
頻繁に実行される軽い SQL アプリケーション側でキャッシュやバッチ化を検討

実行計画の確認方法の詳細は「SQL の実行計画を確認する方法」を参照してください。

実務パターン集

パターン(1): 今まさに遅い SQL をリアルタイム特定

SQL
-- (1) アクティブセッションを確認
SELECT sid, username, sql_id, event, seconds_in_wait
FROM v$session
WHERE status = 'ACTIVE' AND type = 'USER'
ORDER BY seconds_in_wait DESC;

-- (2) 特定した sql_id の SQL テキストを確認
SELECT sql_fulltext FROM v$sql WHERE sql_id = 'abc123def456';

-- (3) 実行計画を確認
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR('abc123def456'));

パターン(2): 昨日の夕方に遅かった SQL を特定(AWR)

SQL
-- (1) スナップショット ID を確認
SELECT snap_id, begin_interval_time
FROM dba_hist_snapshot
WHERE begin_interval_time >= TRUNC(SYSDATE-1) + 17/24  -- 昨日 17:00
  AND begin_interval_time <= TRUNC(SYSDATE-1) + 19/24  -- 昨日 19:00
ORDER BY snap_id;

-- (2) AWR レポートを生成
-- SQL> @$ORACLE_HOME/rdbms/admin/awrrpt.sql
-- 開始 snap_id / 終了 snap_id を指定

パターン(3): 定期的に Top SQL を監視するクエリ

SQL(日次モニタリング用)
-- 毎朝実行: 前日の Top 10 SQL をログテーブルに記録
INSERT INTO sql_perf_log (log_date, sql_id, elapsed_sec, executions, avg_sec)
SELECT TRUNC(SYSDATE-1), sql_id,
       ROUND(elapsed_time/1000000, 2),
       executions,
       ROUND(elapsed_time/NULLIF(executions,0)/1000000, 2)
FROM (
    SELECT sql_id, elapsed_time, executions
    FROM dba_hist_sqlstat s
    JOIN dba_hist_snapshot sn ON s.snap_id = sn.snap_id
    WHERE sn.begin_interval_time >= TRUNC(SYSDATE-1)
      AND sn.begin_interval_time <  TRUNC(SYSDATE)
    ORDER BY elapsed_time DESC
)
WHERE ROWNUM <= 10;
COMMIT;

よくある質問

QV$SQL のデータはいつまで保持されますか?
AV$SQL は共有プール内の SQLを表示します。SQL がキャッシュから押し出されると V$SQL からも消えます。インスタンスを再起動するとすべてクリアされます。過去のデータを保持したい場合は AWR(DBA_HIST_SQLSTAT)を使ってください。
QAWR は Standard Edition で使えますか?
A使えません。AWR / ASH / ADDM は Enterprise Edition + Diagnostics Pack のライセンスが必要です。Standard Edition では Statspack(無料)が代替手段です。Statspack でも Top SQL やイベント統計の分析が可能です。
Qsql_id から実行計画を確認するには?
ASELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR('sql_id', NULL, 'ALL')) で共有プール内の実行計画を確認できます。過去の実行計画は DBMS_XPLAN.DISPLAY_AWR('sql_id') で取得できます。詳細は「実行計画を確認する方法」を参照。
Qelapsed_time と cpu_time の差が大きい SQL は何が問題ですか?
Aelapsed_time - cpu_time待機時間に相当します。この差が大きい SQL は I/O 待ち・ロック待ち・ネットワーク待ちなどで「待っている時間」が長いことを意味します。ASH で待機イベントの内訳を確認して原因を特定してください。
Qbuffer_gets が多い SQL は問題ですか?
Abuffer_gets は論理読み取り(メモリ上のバッファキャッシュ読み取り)の回数です。値が大きい SQL は大量のデータブロックを読んでいることを意味し、インデックスの不足やフルテーブルスキャンが原因の可能性が高いです。実行計画を確認してインデックス追加を検討してください。
QV$SESSION で ACTIVE な SQL がないのにシステムが遅いです
A可能性は複数あります。
(1) バックグラウンドプロセス(DBWR, LGWR 等)がボトルネック → V$SYSTEM_EVENT で確認
(2) PGA / SGA のメモリ不足 → V$PGASTAT / V$SGA で確認
(3) OS レベルの問題(CPU, ディスク, ネットワーク)→ top / iostat / sar で確認
(4) SQL が「瞬間的に」実行完了しているが頻度が高い → V$SQL の executions 列を確認

まとめ

遅い SQL を特定する方法をまとめます。

状況 推奨ツール コマンド / クエリ
今まさに遅い SQL を特定 V$SESSION SELECT sid, sql_id, event FROM v$session WHERE status=’ACTIVE’
経過時間の Top N SQL V$SQL SELECT … FROM v$sql ORDER BY elapsed_time DESC
長時間 SQL の進捗 V$SQL_MONITOR SELECT … FROM v$sql_monitor WHERE status=’EXECUTING’
過去の時間帯の Top SQL AWR レポート @$ORACLE_HOME/rdbms/admin/awrrpt.sql
待機イベントの分析 ASH SELECT sql_id, event FROM v$active_session_history
自動推奨レポート ADDM @$ORACLE_HOME/rdbms/admin/addmrpt.sql
Standard Edition での分析 Statspack @$ORACLE_HOME/rdbms/admin/spreport.sql