DataBase  /  PostgreSQL  /  レポート取得ガイド

EXPLAIN / EXPLAIN ANALYZE による取得とセクション定義

PostgreSQL の EXPLAIN / EXPLAIN ANALYZE コマンドで個々の SQL の 実行計画・コスト推定値・実際の実行時間・バッファ使用量・結合方式・並列処理を取得する方法と、 出力の各セクション・フィールドの定義を詳しく解説します。

1. EXPLAIN コマンドの構文とオプション

SQL — EXPLAIN オプション一覧
-- 基本:実行計画とコスト推定のみ(SQL は実行されない)
EXPLAIN SELECT ...;

-- ANALYZE:実際に実行して実測値を取得(本番注意:DML は本当に実行される)
EXPLAIN ANALYZE SELECT ...;

-- 推奨:全オプション付き(最も詳細な情報)
EXPLAIN (ANALYZE, BUFFERS, VERBOSE, FORMAT TEXT) SELECT ...;

-- JSON 形式で出力(プログラム処理向け)
EXPLAIN (ANALYZE, BUFFERS, FORMAT JSON) SELECT ...;

-- DML でロールバックしながら実行計画を取得する安全なパターン
BEGIN;
EXPLAIN ANALYZE UPDATE orders SET status = 'processed' WHERE id = 1;
ROLLBACK;
オプション説明
ANALYZE SQL を実際に実行し、実測の実行時間・行数を追加で表示する。SELECT 以外は本当にデータが変更されるため注意。
BUFFERS (ANALYZE と組み合わせ)共有バッファのヒット・ミス・ダーティ数・書き込み数を表示。I/O 分析に必須。
VERBOSE 各ノードの出力列リスト・スキーマ名・関数名・ストレージパラメータなど詳細情報を表示。
COSTS コスト推定値を表示する(デフォルト ON)。COSTS OFF で非表示にできる。
SETTINGS (PostgreSQL 12+)デフォルトから変更されたプランナ設定パラメータを表示。
WAL (PostgreSQL 13+, ANALYZE と組み合わせ)WAL レコード数・FPI 数・バイト数を表示。
FORMAT TEXT(デフォルト)/JSON/XML/YAML で出力形式を指定。

2. EXPLAIN ANALYZE 出力例

出力例 — EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT)
Hash Join  (cost=1234.56..5678.90 rows=1000 width=64)
           (actual time=12.345..45.678 rows=987 loops=1)
  Hash Cond: (o.user_id = u.user_id)
  Buffers: shared hit=2340 read=156 dirtied=0 written=0
  ->  Seq Scan on orders o  (cost=0.00..3210.00 rows=100000 width=32)
                             (actual time=0.012..18.234 rows=100000 loops=1)
      Buffers: shared hit=1800 read=120
  ->  Hash  (cost=567.89..567.89 rows=50000 width=32)
             (actual time=8.012..8.012 rows=50000 loops=1)
      Buckets: 65536  Batches: 1  Memory Usage: 3456kB
      Buffers: shared hit=540 read=36
      ->  Index Scan using users_pkey on users u
                             (cost=0.43..567.89 rows=50000 width=32)
                             (actual time=0.034..5.678 rows=50000 loops=1)
          Index Cond: (user_id > 0)
          Buffers: shared hit=540 read=36
Planning Time: 1.234 ms
Execution Time: 46.012 ms

3. ノード行のフィールド定義

フィールド説明Oracle 実行計画 対応
cost=startup..total プランナによるコスト推定値。startup は最初の行を返すまでのコスト、total は全行を返すまでの推定総コスト。単位はプランナ内部の任意単位(ページ読み込みを1.0とした相対値)。 Cost
rows=N このノードが返すと推定される行数。actual rows と大きく乖離する場合は統計情報が古い(ANALYZE が必要)。 Cardinality
width=N 推定される1行あたりの平均バイト幅。
actual time=startup..total (ANALYZE)実際の経過時間(ms)。ループが複数回ある場合は1ループあたりの平均。 A-Time
actual rows=N (ANALYZE)実際に返した行数。推定 rows との差が大きい場合は行数推定が不正確。 A-Rows
loops=N (ANALYZE)このノードが実行されたループ数。Nested Loop の内側ノードは外側の行数分実行される。 Starts
actual time の累積値
actual time は loops が複数の場合、1ループあたりの平均です。 実際の合計時間は actual time × loops で計算します。 ノードの実際の合計時間を確認するには actual time × loops が重要な指標です。

4. アクセスパスの種類と特性

ノード名説明適している場面Oracle 対応
Seq Scan テーブル全件を順次読み込む。フィルタ条件が列名の後に Filter: として表示される。 大量行を返す / インデックスなし / 小テーブル TABLE ACCESS FULL
Index Scan インデックスでキーを検索し、ヒープ(本体テーブル)から行を取得する。ランダム I/O が発生する。 選択率が低い条件 INDEX RANGE SCAN + TABLE ACCESS BY ROWID
Index Only Scan インデックスだけで必要列が揃う場合(カバリングインデックス)。ヒープへのアクセスを最小化。Heap Fetches が 0 に近いほど良い。 SELECT 列がインデックスに含まれる INDEX FAST FULL SCAN
Bitmap Index Scan インデックスからビットマップを生成。複数インデックスの AND/OR 結合が可能。 選択率が中程度 / 複数条件の OR BITMAP INDEX SINGLE VALUE
Bitmap Heap Scan Bitmap Index Scan で生成したビットマップを使って、ヒープをブロック順に読み込む。ランダム I/O を削減。 Bitmap Index Scan の後段 TABLE ACCESS BY ROWID BATCHED
CTE Scan WITH 句(CTE)の結果をスキャンする。PostgreSQL 12 以前はマテリアライズ強制のため、最適化の壁になることがあった。 WITH 句を使うクエリ VIEW PUSHED PREDICATE / INLINE

5. 結合方式の種類と特性

ノード名説明適している場面Oracle 対応
Nested Loop 外側の各行に対して内側をスキャン。内側にインデックスがある場合に有効。大テーブル同士では非常に遅くなる。 外側が少行 / 内側にインデックスあり NESTED LOOPS
Hash Join 小さい方のテーブルをハッシュテーブルに構築し、大きい方でプローブ。work_mem に収まる場合に最速。 等値結合 / 片方が小さい HASH JOIN
Merge Join 両テーブルがソート済みの場合に結合キーを同時スキャン。ソートコストが含まれることが多い。 両テーブルが大きい / 既にソート済み / 範囲結合 MERGE JOIN / SORT MERGE JOIN
Hash Hash Join の内側テーブルをハッシュテーブルに変換するノード。Batches > 1 は work_mem 不足でディスクに溢れている。 Hash Join の内側
⚠️ Hash Join の Batches > 1 は要注意
Batches: 2 以上の表示は、ハッシュテーブルが work_mem に収まらず ディスクに溢れていることを示します。work_mem の増加を検討してください。 ただし全セッションに適用されるためサーバーメモリ全体への影響を考慮してください。

6. BUFFERS オプションの出力定義

フィールド説明Oracle 対応
shared hit=N 共有バッファキャッシュからヒットしたブロック数(物理 I/O なし)。多いほど良い。 Logical Reads(consistent gets)
shared read=N ディスクから読み込んだブロック数(キャッシュミス)。多い場合は shared_buffers の増加か索引の追加を検討。 Physical Reads
shared dirtied=N このクエリがダーティにしたブロック数(UPDATE/DELETE で変更されたブロック)。
shared written=N 実行中にバッファから書き出されたブロック数。通常は 0。 Physical Writes
local hit/read/dirtied/written 一時テーブルのバッファ統計。
temp read/written ソートやハッシュ結合が work_mem を超えてディスクに溢れた場合のブロック数。 Sorts (disk)

7. サマリセクションの定義

フィールド説明Oracle 対応
Planning Time SQL の実行計画を生成するのに要した時間(ms)。通常は数 ms 以下。大きい場合はパーティション数が多すぎる可能性。 Parse / Bind 相当
Execution Time SQL の実際の実行時間(ms)。トリガーや関数の実行時間も含む。 Elapsed Time
JIT (PostgreSQL 11+)JIT コンパイルが行われた場合の詳細(Functions・Generation Time・Inlining Time など)。短いクエリでは JIT のオーバーヘッドが逆効果になることがある。

8. 診断パターンとチューニングの着目点

診断チェックリスト — EXPLAIN ANALYZE の読み方
-- ① rows 推定値と actual rows の乖離確認(統計情報の精度)
-- 推定 rows vs actual rows が 10倍以上ずれている → ANALYZE テーブル名;

-- ② Seq Scan が大テーブルに出現していないか
-- 解決策:WHERE 条件の列にインデックス追加
CREATE INDEX idx_orders_created_at ON orders (created_at);

-- ③ Hash Join の Batches > 1(work_mem 不足)
-- 解決策:セッションレベルで work_mem を増やして再実行
SET work_mem = '256MB';
EXPLAIN (ANALYZE, BUFFERS) SELECT ...;

-- ④ Nested Loop + Seq Scan の組み合わせ(最悪パターン)
-- 内側テーブルにインデックスがない → インデックス追加

-- ⑤ shared read が大きい(ディスク I/O 多い)
-- shared_buffers の増加 or インデックスによる読み込みブロック削減

-- ⑥ temp read/written が 0 でない(work_mem 不足)
-- work_mem の増加 or クエリのリファクタリング
explain.depesz.com / pev2 の活用
複雑な実行計画の視覚化には外部ツールが便利です。 EXPLAIN (FORMAT JSON) の出力を explain.depesz.comPEV2(explain.dalibo.com) に貼り付けると、コストの高いノードをグラフィカルに確認できます。