「とりあえずこのカラムにインデックス貼っておけば速くなるっしょ!」
……後輩くん、もしそんなノリで実装しているなら、一度立ち止まろうか。インデックスは「魔法の杖」じゃない。使い方を間違えると、ただの重荷になるんだ。
今日はPostgreSQLの「複数列インデックス(複合インデックス)」について話そう。ここを正しく理解しているかどうかで、君の書くSQLのパフォーマンスは劇的に変わる。現場でよく見る「なんとなく」の設計を、少しだけプロの視点に変えてみないか?
—
複合インデックスは「電話帳」と同じ
まずイメージしてほしい。君が電話帳を探すとき、どうやって探す?
「苗字」→「名前」という順番で並んでいるから、まず苗字を追いかけて、次に名前を探すよね。
PostgreSQLの複合インデックスもこれと全く同じだ。`CREATE INDEX idx_name ON users (last_name, first_name);` と定義したら、内部的には「苗字でソートされ、苗字が同じなら名前でソートされたリスト」ができあがる。
これが重要なんだ。「左端一致の原則」というやつだね。
「左端一致」を理解しないと、インデックスは空気化する
よくある失敗例を見てみよう。さっきの `(last_name, first_name)` というインデックスがある状態で、こんなクエリを投げたとしよう。
— なぜか遅い…!
SELECT FROM users WHERE first_name = ‘Taro’;
これ、インデックスは使われないんだ。なぜなら、電話帳の「名前」だけを頼りに全ページをめくっているのと同じだから。データベースからすれば「苗字がわからないのに、名前だけ言われても絞り込めないよ!」という状態なんだね。
鉄則:
複合インデックスは、定義した左側のカラムから順に条件を指定しないと機能しない。これが現場で最も多い「インデックスを貼ったのに遅い」の原因だ。
実践:カラムの順序はどう決める?
じゃあ、どのカラムを左側に持ってくるべきか? 答えはシンプルだ。
「カーディナリティ(値の多様性)が高い順」かつ「絞り込み条件として必ず使われるもの」
例えば、ECサイトの注文履歴テーブルを考えてみよう。
`user_id`(誰の注文か)と `created_at`(いつの注文か)で検索することが多いとする。
どっちを先にする?
1. `(user_id, created_at)`
2. `(created_at, user_id)`
もし君のアプリが「特定のユーザーの直近の注文」を頻繁に引くなら、迷わず1だ。`user_id` でガツンと絞り込んでから、日付でソートする。これが最も効率がいい。
逆に、 `(created_at, user_id)` とすると、全期間のデータを日付で探しに行くことになる。日付の範囲が広ければ広いほど、スキャンする範囲は膨大になるよね。
現場で役立つ「等価条件」と「範囲条件」のテクニック
もう一つ、中級者へのステップアップとして覚えておいてほしいのが、「=(等価)」と「範囲条件(> や <)」の組み合わせだ。 例えば `(status, created_at)` という複合インデックスがあったとする。 -- これは爆速 SELECT FROM orders WHERE status = 'shipped' AND created_at > ‘2023-01-01’;
この場合、`status` でバシッと絞り込み、その中から `created_at` を探すので非常に効率的だ。
ポイント:
複合インデックスの順序を決めるときは、「まずは=で指定するカラムを左に、その次に範囲指定するカラムを右に」置くのが鉄則。これでPostgreSQLはインデックスを最大限に活用できる。
最後に:インデックスは「作りすぎない」のも美徳
ここまで話しておいてなんだけど、インデックスにはコストがある。
インデックスを1つ増やすたびに、INSERTやUPDATEのたびにインデックスの書き換えが発生するんだ。特に書き込みの多いテーブルでは、インデックスを貼りすぎると「読み込みは速くなったけど、アプリが重くなった」という本末転倒な事態になる。
- そのクエリは、本当に頻繁に走るのか?
- 他の既存のインデックスで代用できないか?
この自問自答を忘れないでほしい。
エンジニアとしての腕の見せ所は、いかに複雑なクエリを書くかじゃなくて、「いかに少ないリソースで、データベースに効率よく働いてもらうか」だ。
もし今、自分の担当している機能で「なんか遅いな」と思ったら、`EXPLAIN ANALYZE` を叩いて、インデックスがちゃんと使われているか確認してみてくれ。そこからが本当の最適化の始まりだよ。
また何か詰まったら、いつでも聞きに来なよ。一緒にクエリを眺めてみよう。
コメント