【実務・中級編】 インデックススキャン – PostgreSQL

インデックススキャンを「なんとなく」で終わらせない。PostgreSQLの深淵を覗こう。

やあ。データベースのパフォーマンスチューニングに悩む季節がやってきたかな?

普段、何気なく `CREATE INDEX` を叩いて、「よし、これで速くなるはず!」と期待してクエリを投げる。確かに速くはなる。でも、現場で本当のトラブルシューティングにぶち当たった時、「なぜインデックスが使われないのか?」「なぜ思ったほど速くならないのか?」を突き詰めるには、PostgreSQLのインデックススキャンがどう動いているのか、その「心臓部」を知っておく必要があるんだ。

今日は、教科書的な説明はさらっと流して、現場で役立つ「インデックススキャンの正体」について話そうと思う。

—

1. インデックススキャンは「近道」じゃない、ただの「索引」だ

まず、前提を共有させてほしい。インデックススキャンは魔法の近道じゃない。あくまで、膨大なデータの海から目当てのタプル(行)を見つけるための「索引」を辿るプロセスだ。

PostgreSQLにおけるインデックススキャンは、基本的に以下のステップを踏む。

1. インデックスの走査: B-treeなどの構造をルートから辿り、条件に合致する「インデックスエントリ」を見つける。
2. TID(Tuple Identifier)の取得: そのエントリには、実テーブル上のどこにデータがあるかを示す「TID」が記されている。
3. ヒープ(実テーブル)へのアクセス: TIDを頼りに、ディスク上の物理的な場所へジャンプして、実際の行データを読み込む。

ここで重要なのが、「インデックスを読むコスト」+「ヒープを読むコスト」=「インデックススキャンの総コスト」という図式だ。この後者の「ヒープアクセス」が、実は曲者なんだよね。

—

2. なぜ「インデックススキャン」が遅くなることがあるのか?

ある程度経験を積んだエンジニアなら一度は経験するはずだ。「インデックスはあるのに、なぜかSeq Scan(全件走査)の方が速い」という現象。

これは、「ランダムアクセスの罠」に陥っているからだ。

インデックスでTIDを特定し、そこへランダムアクセスしてデータを取りに行く。もし条件に合致する行がテーブル全体に散らばっていたらどうなる? ディスクヘッド(SSDでも似たような話だ)はあちこちをせわしなく飛び回り、シーケンシャルに読むよりも遥かに時間がかかる。

PostgreSQLのオプティマイザは賢いから、「あ、これはインデックスを辿るより、いっそテーブルを全部頭から読んだ方が(シーケンシャルスキャンの方が)速いな」と判断した瞬間、容赦なくインデックスを無視する。これが、君たちが時々目にする「インデックスが効かない」の正体の一つさ。

—

3. 実務で見極める:Index Scan と Index Only Scan

さて、現場で特に意識してほしいのが「Index Only Scan」だ。

もしクエリが `SELECT id, name FROM users WHERE id = 100;` だったとして、インデックスに `id` しか含まれていなければ、PostgreSQLはわざわざヒープまでデータを取りに行く必要がある。

でも、もし `CREATE INDEX idx_users_name ON users(id, name);` としていたら?
実は、インデックスの中に必要なデータがすべて揃っているから、ヒープまで読みに行く必要がない。これをIndex Only Scanと呼ぶ。

— これがインデックスオンリースキャンを誘発するクエリの例
EXPLAIN ANALYZE
SELECT id, name FROM users WHERE id = 100;

`EXPLAIN` を叩いてみて、`Index Scan` ではなく `Index Only Scan` が出ているなら、君のインデックス設計はかなり優秀だ。ヒープへのランダムアクセスを回避できる分、パフォーマンスは劇的に向上する。

—

4. 先輩からのアドバイス:どう使い分けるか

実務でインデックスを設計する時は、以下の3点を意識してみてほしい。

  • Index Only Scanを狙え: カバーリングインデックス(必要なカラムをすべてインデックスに含める手法)は、読み取り負荷の高いシステムでは最強の武器になる。
  • 「カーディナリティ」だけを信じるな: 「ユニークな値が多いカラムにインデックスを貼る」のは定石だけど、それ以上に「そのクエリでどれだけの行がヒットするか」が重要だ。ヒット率が数%を超えるようなら、インデックススキャンはコスト負けする可能性がある。
  • 統計情報の鮮度を疑え: もし「明らかにインデックスを使うべきクエリなのに使われない」なら、`ANALYZE` を忘れていないか確認しよう。統計情報が古ければ、オプティマイザは誤った判断を下す。

—

最後に

インデックススキャンは、PostgreSQLの強力な機能だけど、あくまで「道具」だ。道具は適材適所。

まずは `EXPLAIN ANALYZE` を叩いて、自分が書いたクエリがどういう経路を辿っているのか、実行計画を眺める癖をつけよう。最初は暗号のように見えるかもしれないけれど、慣れてくれば「あ、ここでインデックスが効いて、ここでヒープを引いているんだな」と、データベースの呼吸が聞こえてくるようになるはずだ。

もし何か行き詰まったら、またいつでも相談してくれ。データベースの深淵を一緒に掘り下げていこうじゃないか。

それじゃ、今日はこの辺で。ハッピー・クエリライフを!

コメント

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