DataBase / PostgreSQL / レポート取得ガイド
EXPLAIN / EXPLAIN ANALYZE による取得とセクション定義
PostgreSQL の EXPLAIN / EXPLAIN ANALYZE コマンドで個々の SQL の
実行計画・コスト推定値・実際の実行時間・バッファ使用量・結合方式・並列処理を取得する方法と、
出力の各セクション・フィールドの定義を詳しく解説します。
1. 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 出力
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 オプションの出力定義
セクション定義: EXPLAIN (ANALYZE, BUFFERS) の 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. サマリセクションの定義
セクション定義: EXPLAIN ANALYZE サマリ行
| フィールド | 説明 | 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. 診断パターンとチューニングの着目点
-- ① 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.com や
PEV2(explain.dalibo.com)
に貼り付けると、コストの高いノードをグラフィカルに確認できます。