「とりあえずインデックスを貼る」を卒業しよう。PostgreSQLの「インデックスオンリースキャン」を使いこなす話
現場でパフォーマンスチューニングをしていると、必ずぶつかるのが「クエリが遅い」という悩み。で、よくある対応が「とりあえず関連するカラムにインデックスを貼っておこう」というやつ。
もちろん、それも一つの手だけど、今日はもう一歩先の話をしよう。「インデックスさえあれば速い」というのは半分正解で、半分は誤解だ。今回は、PostgreSQLの隠し玉である「インデックスオンリースキャン(Index Only Scan)」について深掘りしていくよ。
これが使いこなせるようになると、クエリのレスポンスが劇的に改善するし、何より「エンジニアとしての解像度」がグッと上がるからね。
—
インデックスオンリースキャンって結局何なの?
普通、クエリを投げるとPostgreSQLはこういう動きをする。
1. インデックスを見て、該当するデータの場所(ポインタ)を探す。
2. その場所に行って、テーブル本体(ヒープ)からデータを読み出す。
これが一般的な「インデックススキャン」だ。でも、考えてみてほしい。もし、「探したいデータ」が「インデックスの中」に全部揃っていたら、わざわざ重いテーブル本体を見に行く必要ある?
……ないよね。
インデックスの情報だけで完結させて、テーブル本体へのアクセスをゼロにする。これが「インデックスオンリースキャン」だ。ディスクI/Oが激減するから、爆速になるのは当然の話なんだ。
—
実践:どうやって実現するのか?
理屈はわかっても、実務でどう使うかが重要だよね。例えば、ユーザーのメールアドレスを探すクエリを例にしてみよう。
— usersテーブルから特定のIDのメールアドレスを抽出する
SELECT email FROM users WHERE user_id = 12345;
もし、`user_id`だけにインデックスを貼っていたら、PostgreSQLは「メールアドレス」を取りにテーブル本体を見に行かなきゃいけない。でも、もしこうなっていたらどうだろう?
— インデックスにemailを含めちゃう(INCLUDE句)
CREATE INDEX idx_users_id_email ON users (user_id) INCLUDE (email);
こうすると、`user_id`という検索条件だけでなく、`email`という戻り値までインデックスの中に保持される。これで、PostgreSQLはテーブル本体を一切見ることなく、インデックスだけでクエリを完結させられるようになるんだ。
—
ここで「落とし穴」を一つ教えるよ
「じゃあ、全部INCLUDEしちゃえば最強じゃん!」と思ったそこの君、ちょっと待って。ここで一つ、PostgreSQL特有の「Visibility Map(可視性マップ)」という話をしておかないといけない。
インデックスオンリースキャンが発動するための条件は、「インデックス内のデータが最新かどうかが保証されていること」なんだ。
PostgreSQLはMVCC(多版同時実行制御)という仕組みを使っているから、データが更新されると古いバージョンが残る。インデックス側は「このデータあるよ!」と言っていても、実はそのデータが別トランザクションで更新中だったりすると、PostgreSQLは「念のためテーブル本体を見に行って、最新かどうか確認しなきゃ」という判断をするんだ。
つまり、インデックスオンリースキャンを効かせるには、`VACUUM`が適切に走っている必要がある。
「インデックスを貼ったのに、EXPLAINで見たら普通のインデックススキャンになってるんだけど?」という時は、大抵この「可視性」の問題だ。`VACUUM`が追いついていないか、テーブルの更新頻度が激しすぎるのが原因。ここを見落とすと、一生チューニングが終わらないから気をつけてね。
—
まとめ:今日からできること
1. `EXPLAIN ANALYZE`を叩く癖をつける
クエリが遅いなと思ったら、まずこれ。実行計画を見て「Index Only Scan」になっていればOK。もし「Index Scan」になっていたら、工夫の余地がある。
2. `INCLUDE`を検討する
特定のカラムだけ頻繁にSELECTするクエリがあるなら、`INCLUDE`句でインデックスに含めることを検討しよう。
3. VACUUMを味方につける
自動VACUUMの設定が適切か、テーブルの更新負荷に対してリソースが足りているかを確認する。ここが疎かだと、どんなに綺麗なインデックスを貼っても宝の持ち腐れになる。
インデックスオンリースキャンは、データベースの「読み取り」の効率を極限まで引き上げる強力な武器だ。ただの「検索用」だと思っていたインデックスが、「データそのもの」として機能し始める瞬間、データベースエンジニアとしての面白さが分かってくると思うよ。
次はぜひ、手元の重いクエリを`EXPLAIN`して、インデックスオンリースキャンが動くか試してみてくれ。健闘を祈る!
コメント