「テーブルまで見に行く必要なんてないんだよ」。
そう言われてピンとくるかな? PostgreSQLでクエリのパフォーマンスを語るなら、避けて通れないのがIndex-Only Scanの話だ。
「インデックスを貼れば速くなる」というのはエンジニアの共通認識だけど、実はインデックスを使っても、PostgreSQLは最終的にテーブル本体(ヒープ)を覗きに行くことが多い。なぜなら、そのデータが「今、本当に有効なのか(可視なのか)」を確認する必要があるからだ。
でも、条件さえ揃えば、テーブルを一度も開かずに結果を返せる。これがIndex-Only Scan。今日は、この「最強のショートカット」を使いこなすための勘所を、現場の視点で解説していくよ。
—
なぜインデックスだけじゃダメな時があるのか?
まず、大前提の話をしよう。PostgreSQLのMVCC(多版同時実行制御)の仕組み上、インデックスには「この行が現在有効かどうか」という情報が載っていない。
例えば、誰かがデータを更新した直後、古いバージョンの行と新しいバージョンの行がテーブル内に混在するよね。インデックスの指す先を見に行かないと、「お前、今本当に有効なデータなのか?」が判断できないんだ。
そこで登場するのがVisibility Map(VM)という魔法の地図だ。
Visibility Map:テーブルを見に行かないための「証明書」
Visibility Mapは、各ページ(データブロック)に対して「このページ内の全ての行は、誰から見ても有効だよ(可視だよ)」というフラグを管理しているビットマップだ。
PostgreSQLはデータを探すとき、まずこのVMを確認する。
1. VMにフラグがある? → 「このブロック内のデータは全員有効だな。ならテーブル本体を見に行く必要はない!」→ Index-Only Scan発動!
2. VMにフラグがない? → 「念のため、テーブル本体を確認して有効性をチェックしないと…」→ Index Scan(通常)
つまり、インデックスを適切に設計するだけでなく、「VMにフラグを立ててあげること」がパフォーマンス維持の鍵になる。
実践:Index-Only Scanを狙い撃ちする
じゃあ、具体的にどうすればいいか。シンプルな例で見てみよう。
— ユーザーのメールアドレスを探すクエリ
SELECT email FROM users WHERE status = ‘active’;
このクエリでIndex-Only Scanを狙うなら、単に `status` にインデックスを貼るだけじゃダメだ。`email` もインデックスに含める必要がある。
— これが最強の布陣
CREATE INDEX idx_users_status_email ON users (status, email);
こうすれば、`status` で絞り込みつつ、欲しいデータである `email` もインデックスの中に存在することになる。これでPostgreSQLはテーブル本体を一切見ずに、インデックスだけで答えを返せるようになるんだ。
注意点:UPDATEの罠
ここが一番の落とし穴なんだけど、テーブルのデータを頻繁に `UPDATE` すると、Visibility Mapのフラグはすぐに外れてしまう。さっき言った通り、更新があれば「誰から見ても有効」とは言えなくなるからね。
だから、「あまり更新されないマスタデータ」や「追記型のログテーブル」なんかは、Index-Only Scanの恩恵をめちゃくちゃ受けやすい。逆に、頻繁に値が変わるカラムをインデックスに含めても、案外Index-Only Scanにはなってくれないことが多いんだ。
チューニングの確認方法
「自分の書いたSQL、ちゃんとIndex-Only Scanになってる?」と思ったら、迷わず `EXPLAIN ANALYZE` を使おう。
EXPLAIN ANALYZE SELECT email FROM users WHERE status = ‘active’;
出力結果を見て、`Index Scan` ではなく `Index Only Scan` と表示されていれば大成功だ。もし期待通りにいかないなら、`VACUUM` が足りていない可能性がある。`VACUUM` を実行することで、VMが更新され、Index-Only Scanができる状態に戻ることもあるからね。
現場からのアドバイス
最後に一つだけ。「何でもかんでもIndex-Only Scanを狙うな」ということ。
インデックスを大きくしすぎると、今度はメモリ(Shared Buffers)を圧迫して、インデックス自体の読み込みで効率が落ちる。本末転倒だよね。
1. よく使うクエリを特定する。
2. そのクエリに必要な列だけを `INCLUDE` 句(PostgreSQL 11以降なら使える!)でインデックスに持たせる。
3. `VACUUM` が適切に走る環境を整える。
このステップを意識するだけで、データベースのレスポンスは驚くほど変わるはずだよ。
インデックスはただの「検索用」じゃない。「データを持ち運ぶためのカバン」だと思ってみて。そのカバンに何を入れるか、それを考えるのがデータベースエンジニアの腕の見せ所だよ。
さて、今日はここまでにしよう。また何か深掘りしたくなったら、いつでも聞いてくれ。
コメント