3秒でわかる
複数のカラムを順番付きでまとめたインデックス。2つ以上の条件で絞り込む検索を、単独の索引を並べるより確実に速くするために作ります。
もう少し詳しく
どういうものか
複合インデックスは、2つ以上のカラムを並べてひとつの索引にしたものです。CREATE INDEX idx ON orders (customer_id, ordered_at) と書くと、まず customer_id で並び、同じ customer_id の中では ordered_at で並んだ索引ができます。列の順番は索引の性格そのものを決める要素で、入れ替えると別物になります。
なぜ必要か
customer_id の索引と ordered_at の索引を別々に作っても、両方の条件で絞り込む検索は速くなりきりません。多くのデータベースは片方の索引だけを使って候補を集め、残りを1行ずつ確認します。顧客が1万件の注文を持っていれば、その1万件を舐めることになります。
はじめから2列並びの索引があれば、目的の範囲がひとかたまりに並んでいるので、その区間だけを読めば済みます。索引に必要な列がすべて含まれていればテーブル本体を見に行く必要すら無くなり、これをカバリングインデックスと呼びます。
具体例
-- 顧客ごとの注文を日付順に取りたい
CREATE INDEX idx_orders_customer_date
ON orders (customer_id, ordered_at);
-- 索引がそのまま効く
SELECT * FROM orders
WHERE customer_id = 42 AND ordered_at >= '2026-01-01'
ORDER BY ordered_at;
-- 先頭列があるので効く
SELECT * FROM orders WHERE customer_id = 42;
-- 先頭列が条件に無いので、この索引は使われない
SELECT * FROM orders WHERE ordered_at >= '2026-01-01';索引の並び (customer_id, ordered_at)
41 / 2025-12-30
42 / 2025-11-02
42 / 2026-01-05 <- ここから
42 / 2026-02-11
42 / 2026-03-01 <- ここまでを続けて読むだけで済む
43 / 2025-08-19
先頭のcustomer_idで大きく固まり、その中が日付順に並ぶつまずきやすいところ
最大の勘違いは、列の順番を気にせず作ってしまうことです。(customer_id, ordered_at) の索引は customer_id だけの検索にも使えますが、ordered_at だけの検索には使えません。左から連続して条件が揃っている部分までしか効かない、という性質があり、これを左端一致と呼びます。単独で検索する頻度が高い列を先頭に置きます。
範囲条件の位置も落とし穴です。WHERE ordered_at > ? AND status = ? のように範囲条件より後ろに等値条件を置くと、索引はその範囲条件のところで効き目が止まります。等値で絞る列を先、範囲で絞る列を後ろに並べます。
作りすぎも問題になります。索引は更新のたびに書き換えが要るので、INSERT と UPDATE が遅くなり、ディスクも消費します。実際に使われているかは実行計画で確かめます。
EXPLAIN SELECT * FROM orders WHERE customer_id = 42;似た用語との違い
| 語 | 内容 |
|---|---|
| 単一インデックス | 1列だけの索引 |
| 複合インデックス | 複数列を順番付きで並べた索引 |
| カバリングインデックス | 必要な列が索引に全部あり、本体を読まずに済む状態 |