インデックススキャンを「なんとなく」で終わらせない。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` を叩いて、自分が書いたクエリがどういう経路を辿っているのか、実行計画を眺める癖をつけよう。最初は暗号のように見えるかもしれないけれど、慣れてくれば「あ、ここでインデックスが効いて、ここでヒープを引いているんだな」と、データベースの呼吸が聞こえてくるようになるはずだ。
もし何か行き詰まったら、またいつでも相談してくれ。データベースの深淵を一緒に掘り下げていこうじゃないか。
それじゃ、今日はこの辺で。ハッピー・クエリライフを!
コメント