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

「インデックスを貼ったのに、なぜかクエリが速くならない……」

新人エンジニアの頃、僕もよく悩みました。PostgreSQLのパフォーマンスチューニングにおいて、インデックスは「魔法の杖」のように思われがちですが、実際には「使い方を間違えるとただの重りになる」という、かなり繊細なツールです。

今日は、PostgreSQLにおける「インデックススキャン」の仕組みと、現場で必ず直面する「落とし穴」について、少し深掘りして解説してみようと思います。

—

インデックススキャンは「本の索引」じゃない?

よく「インデックス=本の索引」と例えられますよね。確かに概念としては正しいんですが、DBのインデックスはもっとシビアです。

PostgreSQLで最も一般的な「B-treeインデックス」は、データをツリー状に並べています。クエリが走ると、DBは根(ルート)から順に枝を辿り、目的のデータ(葉ノード)を探し当てます。

ここで重要なのは、「インデックススキャン」とは、そのツリーを辿った後に「実際のテーブルデータを見に行く(ヒープフェッチ)」という二段階のプロセスだということです。

具体例を見てみよう

例えば、ユーザーテーブルがあるとします。

— ユーザーIDで検索するクエリ
EXPLAIN ANALYZE
SELECT FROM users WHERE user_id = 12345;

このとき、PostgreSQLは以下の手順を踏みます。
1. Index Scan: B-treeを辿って「12345」というキーに対応する場所(ポインタ)を見つける。
2. Heap Fetch: そのポインタを頼りに、テーブルの実際のデータ(ヒープ)にアクセスして中身を取り出す。

この「2番」のアクセスが実はコストなんです。もし取得したいカラムがインデックスの中に全部含まれていれば、わざわざテーブルを見に行かなくても済む……これが後に話す「Index Only Scan」という最適化のヒントになります。

—

現場でよくある「なぜか遅い」原因

インデックスを貼ったはずなのに、`EXPLAIN` を叩くと `Seq Scan`(全件スキャン)になっている……。これ、あるあるですよね。原因の多くは以下の3つです。

1. インデックス列に関数を噛ませている

— これだとインデックスは効かない!
SELECT FROM users WHERE LOWER(email) = ‘test@example.com’;

PostgreSQLは `email` というカラムにはインデックスを貼っていますが、`LOWER(email)` という「変換後の結果」にはインデックスを貼っていません。この場合は「関数インデックス(Functional Index)」を作るのが正解です。

2. データ型の不一致

— emailがvarchar型だとして
SELECT FROM users WHERE email = 12345;

暗黙の型変換が発生すると、インデックスは無視されることがあります。型は常に合わせる。これ、基本中の基本ですが意外と踏みます。

3. 「カーディナリティ」が低すぎる

例えば「性別」のような、値の種類が極端に少ないカラムにインデックスを貼っても、DBのオプティマイザは「これ全件スキャンしたほうが速いな」と判断します。インデックスが効くのは、抽出結果がテーブル全体の数パーセント程度に収まるようなケースが多いですね。

—

実践:Index Only Scan を狙え!

パフォーマンスを極限まで絞り出したいとき、僕が一番意識するのは 「Index Only Scan」 です。

先ほど、「インデックススキャンはヒープフェッチが重い」と言いました。なら、「インデックスの中に必要なデータ全部入れちゃえば、ヒープ見に行かなくていいよね?」 というのがこの考え方です。

PostgreSQLでは `INCLUDE` 句を使います。

— emailで検索するけど、同時にnameもよく取得する場合
CREATE INDEX idx_users_email_name ON users (email) INCLUDE (name);

こうしておくと、`SELECT name FROM users WHERE email = ‘…’` というクエリに対し、インデックスの領域だけで処理が完結します。ディスクI/Oが激減するので、爆速になりますよ。

—

最後に:先輩からのアドバイス

インデックスは「貼れば貼るほどいい」というわけではありません。インデックスもデータなので、データを更新(INSERT/UPDATE)するたびに、インデックスも書き換えが必要になります。つまり、貼りすぎると更新処理が重くなります。

僕が新人さんにいつも言っているのはこれです。

「EXPLAINを友だちにしよう」

クエリを書いたら必ず `EXPLAIN (ANALYZE, BUFFERS)` をつけて実行してください。どこで時間を食っているか、インデックスは本当に使われているか。画面の向こう側のDBがどう動いているかを想像できるようになると、エンジニアとして一つ上のステージに上がれます。

また次回、今度は「複合インデックスの順序の決め方」について、さらにディープな話をしましょうか。それでは、良いパフォーマンスライフを!

コメント

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