EXPLAIN ANALYZEとは

EXPLAINはクエリの実行計画を表示するコマンドである。ANALYZEオプションを付けると、実際にクエリを実行し実行時間や実際の処理件数も表示する。

EXPLAIN ANALYZE SELECT * FROM accounts WHERE balance = 27423;

以下のような結果が得られる。

 Seq Scan on accounts  (cost=0.00..1886.00 rows=2 width=17) (actual time=0.004..3.863 rows=1 loops=1)
   Filter: (balance = 27423)
   Rows Removed by Filter: 99999
 Planning Time: 0.129 ms
 Execution Time: 3.880 ms

EXPLAINのみの場合は実行計画の見積もりのみを表示し、実際にクエリは実行しない。実行時間を伴わない分、本番データを変更するリスクなく実行計画を確認できる。

Seq ScanとIndex Scan

実行計画の1行目にはスキャン方式が表示される。Seq Scanはテーブルの全行を先頭から順に読み込む方式である。上記の例ではaccountsテーブルのbalance列に対してインデックスがないため、Seq Scanが選択されている。

主キーであるid列で検索すると、スキャン方式が変わる。

EXPLAIN ANALYZE SELECT * FROM accounts WHERE id = 27423;
 Index Scan using accounts_pkey on accounts  (cost=0.29..8.31 rows=1 width=17) (actual time=0.013..0.013 rows=1 loops=1)
   Index Cond: (id = 27423)
 Planning Time: 0.163 ms
 Execution Time: 0.037 ms

Index Scanはインデックスを使って対象行を絞り込んでから読み込む方式である。id列には主キー制約によるインデックスがあるため、全行を読まずに対象行を特定できる。Index Condには検索に使われたインデックスの条件が表示される。

Seq Scan側のFilterRows Removed by Filterは、条件に一致しなかった行をフィルタで除外したことを示す。Rows Removed by Filter: 99999は、10万行のテーブルから条件に一致する1行を見つけるために99999行を読み捨てたことを意味する。対象行が少ないテーブルであっても、インデックスがない列で検索すると全行を読む必要があり非効率になる。

コストの読み方

cost=0.00..1886.00のような数値は、PostgreSQLのプランナが見積もったコストである。単位は秒などの時間ではなく、プランナ内部で使われる相対的なコスト値である。

  • 1つ目の数値(startup cost): 最初の1行を返すまでに必要なコスト
  • 2つ目の数値(total cost): 全行を返すまでに必要なコスト

Seq Scancost=0.00..1886.00は、最初の行を返すコストは0.00、全行を返すコストは1886.00であることを示す。一方Index Scancost=0.29..8.31は、どちらもはるかに小さい値である。インデックスを使うと読み込む行数が少なく済むため、コストが低く見積もられている。

rowsはプランナが見積もった返却行数、widthは1行あたりの推定バイト数である。プランナはこれらの見積もりに基づいて実行計画を選択する。複数の実行方式が候補にある場合は、コストが最も低い方式を採用する。

実行時間の読み方

ANALYZEオプションを付けると、actual time以降に実際の実行結果が表示される。

(actual time=0.004..3.863 rows=1 loops=1)
  • actual timeの1つ目の数値: 最初の1行を返すまでにかかった実際の時間(ミリ秒)
  • actual timeの2つ目の数値: 全行を返すまでにかかった実際の時間(ミリ秒)
  • rows: 実際に返却された行数(loopsが2以上の場合は1回あたりの平均行数)
  • loops: そのノードが実行された回数

rowsはコスト欄の見積もり行数と対応している。桁違いに乖離している場合は、統計情報の更新を検討する。統計情報を更新するにはANALYZEコマンドを実行する。

ANALYZE accounts;

loopsは、結合処理などでノードが複数回実行されると2以上になる。loopsが2以上のノードでは、actual timerowsはいずれも1回あたりの平均値であるため、ノード全体の所要時間や処理行数はそれぞれにloopsを掛けた値になる。この点に注意する。

最終行のPlanning Timeは実行計画を作成するのにかかった時間、Execution Timeはクエリの実行全体にかかった時間である。