基礎読めるようになる

EXPLAIN と EXPLAIN ANALYZE の違い(loops は 1 回あたりの平均)

定義

EXPLAIN は見積りだけを返すが、EXPLAIN ANALYZE は実際にクエリを実行して実測値も返す。actual time は「初行まで / 全行まで」の 2 つで、loops が 1 より大きいノードでは 1 回あたりの平均が表示される。総量に戻すには loops を掛ける。

見積りと実測を並べて出す

EXPLAIN は見積りだけを返します。EXPLAIN (ANALYZE) を付けると実際にクエリを実行して、 実測値も一緒に返します。

EXPLAIN (ANALYZE) SELECT ...;

すると各ノードにカッコが 2 つ並びます。

(cost=0.43..266.09 rows=23 width=12) (actual time=0.005..0.005 rows=2.00 loops=250000)
  • 前半(cost=…)が見積り
  • 後半(actual …)が実測

この 2 つを見比べるのが EXPLAIN ANALYZE の主目的です。見積りが実測から大きく外れていれば、そのノードを起点に計画が壊れています。

ANALYZE は本当に実行します。UPDATE / DELETE / INSERT に付けるとデータが変わります。試すならBEGIN;ROLLBACK; で囲みます。

actual time は 2 つある

actual time=0.005..0.005 の左は最初の 1 行まで、 右は全部返すまでです。cost の 2 つと対応しています。

Sort のように全部読まないと 1 行も返せないノードは、 左が大きくなります。逆に Seq Scan は 1 行目をすぐ返せるので左が小さいままです。

loops があると、表示は 1 回あたりの平均になる

ここがこのページでいちばん大事な話です。

あるノードが繰り返し実行される場合、loops= に回数が入ります。 そしてactual timerows も、 loops で割った平均が表示されます。

 Limit  (cost=196523.68..196523.70 rows=10 width=30) (actual time=2160.299..2160.379 rows=10.00 loops=1)
   ->  Sort  (cost=196523.68..196526.29 rows=1046 width=30) (actual time=2160.293..2160.320 rows=10.00 loops=1)
         Sort Key: (sum((i.price * i.qty))) DESC
         Sort Method: top-N heapsort  Memory: 26kB
         ->  GroupAggregate  (cost=196477.54..196501.07 rows=1046 width=30) (actual time=2090.929..2158.921 rows=20000.00 loops=1)
               Group Key: c.name
               ->  Sort  (cost=196477.54..196480.15 rows=1046 width=22) (actual time=2090.883..2131.767 rows=500000.00 loops=1)
                     Sort Key: c.name
                     Sort Method: external merge  Disk: 16656kB
                     ->  Nested Loop  (cost=597.43..196425.08 rows=1046 width=22) (actual time=3.118..1561.364 rows=500000.00 loops=1)
                           ->  Hash Join  (cost=597.00..57141.20 rows=523 width=18) (actual time=3.087..177.650 rows=250000.00 loops=1)
                                 Hash Cond: (o.customer_code = c.code)
                                 ->  Seq Scan on orders o  (cost=0.00..56537.00 rows=526 width=11) (actual time=0.129..127.002 rows=250000.00 loops=1)
                                       Filter: ((ordered_at >= '2026-07-01'::date) AND (status = 'shipped'::text) AND (channel = 'web'::text) AND (payment = 'card'::text))
                                       Rows Removed by Filter: 1750000
                                 ->  Hash  (cost=347.00..347.00 rows=20000 width=21) (actual time=2.844..2.847 rows=20000.00 loops=1)
                                       Buckets: 32768  Batches: 1  Memory Usage: 1309kB
                                       ->  Seq Scan on customers c  (cost=0.00..347.00 rows=20000 width=21) (actual time=0.053..1.144 rows=20000.00 loops=1)
                           ->  Index Scan using order_items_order_id_idx on order_items i  (cost=0.43..266.09 rows=23 width=12) (actual time=0.005..0.005 rows=2.00 loops=250000)
                                 Index Cond: (order_id = o.id)
                                 Filter: (qty = 3)
                                 Rows Removed by Filter: 4
                                 Index Searches: 250000
 Planning Time: 3.419 ms
 Execution Time: 2162.557 ms

内側の Index Scan に loops=250000 が付いている。
PostgreSQL 18.6 / 2026-08-22 採取 / 並列・JIT は OFF

この計画の内側はこう書いてあります。

(actual time=0.005..0.005 rows=2.00 loops=250000)
  • 1 回あたり 0.005 ミリ秒
  • 1 回あたり 2 行
  • それが 250,000 回

総量に戻すには掛けます。0.005 × 250000 = 1250ms2 × 250000 = 500,000 行表示上いちばん小さい数字を持つノードが、実は 1.25 秒使っています。

PostgreSQL 18 から rows が小数で出ますrows=2.00)。 これは平均だからです。 17 までは整数に丸められていたので、平均であることが見た目から分かりませんでした。

この変更は PostgreSQL 18 のリリースノートに載っています。同じバージョンで EXPLAIN (ANALYZE)BUFFERS が既定で有効になり、Index Searches の行も増えました。

上の行の時間は「その下で起きたこと全部」を含む

actual time は累積です。あるノードの時間には、その子で使った時間が全部入っています。だから全ノードを足すと二重に数えることになります。

そのノード「だけ」の時間を知るには引き算します。

自分の時間 = 自分の actual time 上端 × loops
             - Σ (子の actual time 上端 × 子の loops)

子にも loops を掛けるのを忘れないでください。忘れると答えが変わります。実際にやってみます。

loops を掛けずに引き算した場合(誤答)
ノード自分の時間
Nested Loop1383.7ms(64.0%)
Sort570.4ms(26.4%)
Seq Scan on orders o127.0ms(5.9%)
Hash Join47.8ms(2.2%)
GroupAggregate27.2ms(1.3%)
Index Scan using order_items_order_id_idx on order_items i10 / 10 位)0.0ms(0.0%)
loops を掛けて引き算した場合(正しい)
ノード自分の時間loops
Index Scan using order_items_order_id_idx on order_items i1250.0ms(57.8%)250000
Sort570.4ms(26.4%)1
Nested Loop133.7ms(6.2%)1
Seq Scan on orders o127.0ms(5.9%)1
Hash Join47.8ms(2.2%)1

左が掛け忘れた場合、右が正しい場合です。Nested Loop が 1 位から 3 位に落ち、 代わりに内側の Index Scan が 1 位に上がりました。

この引き算を全ノードでやって並べるのが、遅いノードの見つけ方の手順そのものです。

丸めの影響

actual time はミリ秒 3 桁で丸められます。0.005 と表示されていても、実際は 0.0045〜0.0055 の幅があります。

loops が大きいと、この誤差も loops 倍されます。25 万回なら ±125ms。だから loops の大きいノードの絶対値は、桁として読むのが正しい付き合い方です。

引き算した結果が負になることもあります。 そのときは親のサブツリー時間が上限を教えてくれます (親から、丸めの増幅を受けていない側の子を引いた値が、残りの子の上限)。

まとめ

  • EXPLAIN ANALYZE実際に実行する
  • actual time は「初行まで / 全部まで」の 2 つ
  • loops があると表示は 1 回あたりの平均。総量は掛け算で戻す
  • 上の行の時間は累積。自分の時間は引き算で出す

よくある疑問

Q.actual time の 2 つの数字は何ですか?
A.左が最初の 1 行を返すまで、右が全部返し終わるまでの実測時間です。cost の 2 つと対応しています。ソートのように全部読まないと 1 行も返せないノードは左右がほぼ同じになります。
Q.loops が 1 より大きいとき、時間は合計ですか平均ですか?
A.平均です。actual time も actual rows も loops で割った値が表示されます。総量に戻すには loops を掛けます。ここを掛け忘れるのが、実行計画でいちばん多い読み違えです。
Q.全ノードの時間を足しても Execution Time になりません
A.上の行の時間はその下で起きたこと全部を含む累積なので、単純に足すと二重に数えます。あるノードだけの時間を知るには、そのノードから子のぶんを引きます。

関連トピック

もっと学びたい方へ(おすすめ書籍)

SQL実践入門 ── 高速でわかりやすいクエリの書き方
ミック

「なぜこの書き方が速いのか」を実行計画から説明する一冊。条件分岐・集約・結合・更新のそれぞれで、良い書き方と悪い書き方を対比しながら読める。

Amazon で見る →
おすすめ
達人に学ぶDB設計徹底指南書 第2版
ミック

テーブル設計と正規化、パフォーマンス考慮のインデックス設計まで実務レベルで学べる定番書。第2版ではクラウド対応も強化。

Amazon で見る →
おすすめ
楽々ERDレッスン (CodeZine BOOKS)
羽生章洋

ER 図をどう「使える設計」に落とすか、実務の判断まで踏み込んだ入門書。エンティティの切り出しから多対多の扱いまで具体例が豊富。

Amazon で見る →
おすすめ
スッキリわかるSQL入門 第4版 ドリル256問付き!
中山清喬/飯田理恵子

ドリル 256 問を実際に打ちながら進める SQL の入門書。付属のブラウザ環境で演習できるので、SELECT から結合・集約までを環境構築で止まらずに通せる。

Amazon で見る →
達人に学ぶSQL徹底指南書 第2版
ミック

SQLの本質的な使い方と、インデックスが効くクエリの書き方を学べる。ウィンドウ関数など現代SQLも網羅。

Amazon で見る →
SQLアンチパターン 第2版
Bill Karwin

実務でやりがちなSQL・DB設計のアンチパターンとその回避策を体系的に学べる。

Amazon で見る →
[改訂3版]内部構造から学ぶPostgreSQL
勝俣智成 ほか

PostgreSQLの内部構造・ストレージ・インデックス機構を丁寧に解説。設計と運用計画の鉄則が学べる。

Amazon で見る →
情報処理教科書 データベーススペシャリスト 2025年版
三好康之

IPAデータベーススペシャリスト試験の総合対策書。インデックス関連は本サイトと合わせて学ぶと理解が深まる。

Amazon で見る →
理論から学ぶデータベース実践入門
奥野幹也

リレーショナルモデルの理論から、インデックス設計を含む実務で使えるSQLまで解説。

Amazon で見る →

本セクションはAmazonアソシエイトのリンクを含みます。

オンライン個別指導

もっと深くDBを学びたい方へ。

たいてっくが、SQL・データベース設計・パフォーマンスチューニング・IPAデータベーススペシャリスト対策まで、1対1で学習をサポートします。まずは無料相談から。

無料相談を予約する →
受講者の声 (DB・SQL コース)
menta レビュー原文を見る →
  • 教え方も上手で、お人柄も良いメンターです。DB周りの知識はもちろん、何より、しっかり教えてあげようという姿勢がとてもありがたかったです。データベース、SQLの学習を考えている方にはおススメです。
    H 様DB・SQL コース受講
  • 体系的に知識を教えてくださり、実際の業務でも大変役立っております。特に短い時間で効率よく知識の習得や、練習をできているのは期待以上でした。
    M 様DB・SQL コース受講
  • 大変充実したコンテンツでわかりやすいご説明をありがとうございました。基本的な質問にも丁寧にご説明いただき、また業務のご相談にも乗って頂き大変有意義な時間でした。
    K 様DB・SQL コース受講