EXPLAINを読み解く
推測をやめて計画を見る
速度の話は推測が入り込みやすい領域です。「たぶんここが重い」で索引を足しても、外れていれば書き込みが重くなるだけです。
EXPLAIN は、データベースがこのクエリをどう処理するつもりかを教えてくれます。読み方さえ分かれば、推測が要らなくなります。
内側から外側へ読む
計画は入れ子になっています。深くくぼんだ行が先に実行され、その結果が外側へ渡ります。
Aggregate
-> Bitmap Heap Scan on orders
Recheck Cond: (customer_id = 777)
-> Bitmap Index Scan on orders_customer_id_idx
Index Cond: (customer_id = 777)読む順番は下から上です。索引で該当箇所を見つけ、その行を取り出し、最後に集計している、と読めます。
走査の種類
最初に見るのが、テーブルをどう読むかです。
| 種類 | 意味 |
|---|---|
Seq Scan | 全部の行を先頭から読む |
Index Scan | 索引をたどって、必要な行だけ読む |
Bitmap Index Scan | 索引で該当箇所の印を集めてから、まとめて読む |
Index Only Scan | 索引の中だけで完結し、表を読まない |
該当行が少ないときは Index Scan、そこそこ多いときは Bitmap が選ばれます。どちらも索引を使っているので、どちらでも構いません。
Index Cond と Filter を見分ける
走査の種類より重要なのが、条件がどこに書かれているかです。
| 表示 | 意味 |
|---|---|
Index Cond | 索引の側で絞り込めている |
Recheck Cond | 索引で絞ったあとの確認。問題無い |
Filter | 行を全部読んでから捨てている |
Filter が付いている行は、そこで大量に捨てているなら改善の余地があります。
見積もり行数を疑う
各行の末尾には rows= という見積もりが出ます。データベースが統計情報から予測した件数です。
Bitmap Heap Scan on orders (cost=4.33..23.06 rows=5 width=20)この見積もりが実際と大きく食い違っていると、選ばれた計画そのものが的外れになります。見積もり5件のつもりが実際は5万件、というときは ANALYZE を打って統計を作り直します。
実際に流して本当の件数を見たいときは EXPLAIN ANALYZE を使います。ただし本当に実行されるので、UPDATE や DELETE に対して打つときは注意します。
この教材の環境について
ブラウザの中で動く PostgreSQL は、索引が使えるなら必ず索引を選ぶ設定になっています。そのため全表走査が選ばれたときの cost が極端に大きな値で表示されます。数字そのものではなく、どの走査が選ばれ、条件がどこに書かれたかを見てください。
手を動かす
3通りのクエリの計画を読んでから、実際の集計を出します。
テーブル構造
CREATE TABLE orders (
id integer PRIMARY KEY,
store_id integer NOT NULL,
customer_id integer NOT NULL,
ordered_on date NOT NULL,
status text NOT NULL,
total integer NOT NULL
);
INSERT INTO orders
SELECT g,
1 + (g % 5),
1 + (g % 20000),
DATE '2026-01-01' + (g % 366),
(ARRAY['paid', 'shipped', 'cancelled'])[1 + (g % 3)],
1000 + (g % 50000)
FROM generate_series(1, 100000) AS g;
CREATE INDEX orders_customer_id_idx ON orders (customer_id);
ANALYZE orders;
期待される出力
| status | cnt |
|---|---|
| cancelled | 33333 |
| paid | 33333 |
| shipped | 33334 |