「なぜか速い」の正体を知る:PostgreSQLのインデックスオンリースキャン(Index Only Scan)を使いこなそう
やあ。データベースのパフォーマンスチューニングに悩む君、今日も一日お疲れ様。
PostgreSQLを触っていると、「EXPLAINで実行計画を見ると、なぜかインデックスだけで処理が終わっている時があるな」と気づいたことはないかな? 以前、若手のエンジニアから「これってバグじゃないんですか?」なんて聞かれたことがあるんだけど、実はこれこそがPostgreSQLの隠れた「高速化の切り札」、インデックスオンリースキャンなんだ。
今日は、この「最強の時短術」について、現場の勘所を交えて解説していくよ。
—
インデックスオンリースキャンとは何か?
基本のおさらいだけど、通常、PostgreSQLがインデックスを使ってデータを検索する際の流れはこうだ。
1. インデックスを辿って、目的のデータの場所(TID: Tuple Identifier)を探す。
2. そのTIDを元に、ヒープ(テーブル本体のデータ領域)にアクセスして、必要なカラムの値を取り出す。
この「ヒープへのアクセス」っていうのが、実は結構なコストなんだ。物理ディスクへのアクセスが発生したり、メモリ(共有バッファ)の取り合いになったりするからね。
インデックスオンリースキャンは、この「ヒープへのアクセス」を省略する手法だ。もし「抽出したいデータ」が「インデックスの中」にすべて含まれていれば、わざわざヒープを見に行く必要なんてないよね。インデックスのデータだけでクエリを完結させてしまう。これがインデックスオンリースキャンだ。
—
具体例で見てみよう
例えば、ユーザーのメールアドレスを検索するテーブルがあったとする。
CREATE TABLE users (
id SERIAL PRIMARY KEY,
username TEXT,
email TEXT
);
— メールアドレスでよく検索するからインデックスを貼る
CREATE INDEX idx_users_email ON users(email);
ここで、こんなクエリを実行してみよう。
EXPLAIN ANALYZE SELECT email FROM users WHERE email = ‘test@example.com’;
この時、PostgreSQLのオプティマイザは「お、`email`ならインデックスの中に情報が全部あるじゃん。ヒープは見なくていいな」と判断して、Index Only Scanを選択してくれる。
ここで注意点!「Visibility Map」の存在
ここからが少し深い話だよ。実は、インデックスに値があるだけじゃ不十分なんだ。PostgreSQLはMVCC(多版同時実行制御)という仕組みを使っているから、「そのデータが本当に最新か、他のトランザクションから見えていいものか」をヒープを見に行って確認する必要がある。
ただ、これだと毎回ヒープを見に行くことになってインデックスオンリーにならないよね。そこで登場するのがVisibility Map(可視性マップ)だ。PostgreSQLは、「このページのデータは全員から見えている(=最新で確定している)」という情報を別のマップで管理している。
つまり、「インデックスに値がある」かつ「そのデータがVisibility Map上で『全員から見える』と判定されている」という二つの条件が揃ったときだけ、真のインデックスオンリースキャンが発動するんだ。
—
実務で「意図的に」使うためのテクニック:INCLUDE句
「でも、検索条件以外のカラムも取得したいときはどうするの?」という疑問が湧くはずだ。例えば `SELECT email, id FROM users WHERE email = ‘…’` の場合、インデックスには `email` しかないから、結局ヒープを見に行くことになる。
そんな時に便利なのが、PostgreSQL 11から導入された `INCLUDE` 句 だ。
CREATE INDEX idx_users_email_include_id ON users(email) INCLUDE (id);
こうすると、検索には `email` を使いつつ、インデックスの「おまけ」として `id` のデータもインデックス内に保持させることができる。これで、`SELECT email, id` のクエリもインデックスオンリースキャンで爆速になるわけだ。
これ、大規模なログテーブルや、読み取り専用に近いマスタテーブルで使うと、レスポンス速度が劇的に変わることがあるよ。ぜひ試してみてほしい。
—
先輩からのアドバイス:やりすぎは禁物
「じゃあ、すべてのカラムをインデックスに入れちゃえばいいじゃないか!」と思うかもしれない。でも、それは悪手だ。
- インデックスの肥大化: インデックスが大きくなれば、メモリに乗らなくなって逆に検索効率が落ちる。
- 更新コスト: テーブルを更新するたびに、インデックスの書き換えコストも増える。
インデックスオンリースキャンは「頻繁に実行される、かつパフォーマンスがクリティカルなクエリ」に対してピンポイントで使うのが職人のやり方さ。
—
まとめ
- インデックスオンリースキャンは、ヒープアクセスを省略して高速化する手法。
- 実現には「インデックスに全列があること」と「Visibility Mapで可視性が確認できること」が必要。
- `INCLUDE` 句を使えば、検索条件には使わないカラムもインデックスに含めて最適化できる。
データベースのチューニングは、パズルみたいで面白いだろ? 「なぜ速いのか」「なぜ遅いのか」を論理的に分解できるようになると、君のエンジニアとしての武器はグッと増えるはずだ。
また何か気になったことがあったら、いつでも聞きに来てくれ。じゃ、コード書きに戻ろうか。
コメント