【実務・中級編】 Index-Only Scansの条件 – PostgreSQL

「テーブルまで見に行く必要なんてないんだよ」。

そう言われてピンとくるかな? 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` が適切に走る環境を整える。

このステップを意識するだけで、データベースのレスポンスは驚くほど変わるはずだよ。

インデックスはただの「検索用」じゃない。「データを持ち運ぶためのカバン」だと思ってみて。そのカバンに何を入れるか、それを考えるのがデータベースエンジニアの腕の見せ所だよ。

さて、今日はここまでにしよう。また何か深掘りしたくなったら、いつでも聞いてくれ。

コメント

タイトルとURLをコピーしました