「インデックスを貼ったのに、なぜかクエリが速くならない……」
新人エンジニアの頃、僕もよく悩みました。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がどう動いているかを想像できるようになると、エンジニアとして一つ上のステージに上がれます。
また次回、今度は「複合インデックスの順序の決め方」について、さらにディープな話をしましょうか。それでは、良いパフォーマンスライフを!
コメント