Postgres: INCLUDE 付きのカバリングインデックスで Index Only Scan にする
CREATE INDEX ... ON t (検索列) INCLUDE (取り出す列, ...) とすると、SELECT する列もインデックスに持たせられる。テーブル本体(ヒープ)を読まずにインデックスだけで答えられるので、よく叩かれる絞り込みクエリが速くなる。
使う場面
EXPLAIN ANALYZE で Index Scan になっているのに遅く、インデックスで見つけた行ごとにテーブル本体を読みに行っているとき。たとえば「ユーザーの注文一覧で、ID と金額と日時だけ出す」ような、取り出す列が少なく決まっているクエリ。
やり方
sql
-- よく実行されるクエリ
SELECT id, amount, created_at
FROM orders
WHERE customer_id = $1;
-- 検索に使う列はキーに、取り出すだけの列は INCLUDE に
CREATE INDEX CONCURRENTLY orders_customer_covering
ON orders (customer_id) INCLUDE (id, amount, created_at);確かめ方:
sql
EXPLAIN (ANALYZE, BUFFERS)
SELECT id, amount, created_at FROM orders WHERE customer_id = 42;
-- Index Only Scan using orders_customer_covering ...
-- Heap Fetches: 0 ← 0 か小さければヒープを読んでいない注意点
- Postgres 11 以降の機能。
Heap Fetchesが多いままなら、可視性マップが古い。Index Only Scan でも「その行が全員から見えるか」を確かめるためにヒープを読みに行く。VACUUM(autovacuum)が追いついているか確認する。更新の多いテーブルほど効果が薄い。- INCLUDE の列は検索条件や並び替えには使われない。
WHEREやORDER BYに使う列はキー側に置く。 - UNIQUE インデックスに INCLUDE を付けても、一意性の判定はキー列だけで行われる。
- インデックスが大きくなり、書き込みのたびに更新されるコストも増える。全部の列を入れず、本当に頻繁なクエリだけに絞る。
- 本番では
CONCURRENTLYで作る。失敗したときの後始末は PostgreSQL: 失敗した CREATE INDEX CONCURRENTLY は INVALID なインデックスを残す。関数を通した検索には Postgres: lower(email) で検索するなら式インデックスを張る を使う。