MySQL の複合インデックスで列の順序が重要な理由
複合インデックスは複数のカラムを組み合わせたインデックスです。単一カラムのインデックスを複数作るより効率的な場合がありますが、列の順序を間違えると期待した効果が得られません。
複合インデックスの基本
複合インデックスは、指定した順序でカラムを並べて作成します。
ALTER TABLE orders ADD INDEX idx_user_status (user_id, status);このインデックスは user_id → status の順序で構造化されています。これが重要なポイントです。
左端プレフィックスルール
複合インデックスは、左端のカラムから順に使われます。途中のカラムをスキップすることはできません。
-- インデックス: (user_id, status, created_at)
-- ○ インデックス有効
WHERE user_id = 100
WHERE user_id = 100 AND status = 'pending'
WHERE user_id = 100 AND status = 'pending' AND created_at > '2024-01-01'
-- × インデックス無効(左端がない)
WHERE status = 'pending'
WHERE created_at > '2024-01-01'
WHERE status = 'pending' AND created_at > '2024-01-01'電話帳で例えると、「田中」さんを探すのは簡単ですが、「名前が太郎の人」を探すには全ページをめくる必要があるのと同じです。
列の順序の決め方
では、どの順序でカラムを並べるべきでしょうか。基本的な指針は以下のとおりです。
= で検索するカラムを先に配置します。範囲条件(>, <, BETWEEN)のカラムは後ろに置きます。
値の種類が多いカラムを先に配置すると、絞り込み効率が上がります。ただし、クエリパターンとの兼ね合いも重要です。
具体例で考える
注文テーブルで、特定ユーザーの特定ステータスの注文を日付順に取得するクエリを考えます。
SELECT * FROM orders
WHERE user_id = 100 AND status = 'pending'
ORDER BY created_at DESC;この場合、最適なインデックスは (user_id, status, created_at) です。
user_id = 100 で絞り込み
status = ‘pending’ でさらに絞り込み
created_at でソート(filesort 不要)
もし (user_id, created_at, status) の順序だと、status での絞り込みがインデックスで行えず、効率が落ちます。
範囲条件の影響
範囲条件(>, <, BETWEEN, LIKE 'abc%')を使うと、それより右のカラムはインデックスが使われません。
-- インデックス: (a, b, c)
WHERE a = 1 AND b > 10 AND c = 'x'
-- a と b までインデックス使用、c は使われないそのため、等価条件を左に、範囲条件を右に配置するのが原則です。
ORDER BY との組み合わせ
インデックスの順序と ORDER BY の順序が一致すると、ソート処理(filesort)を省略できます。
INDEX (user_id, created_at) + ORDER BY created_at
INDEX (user_id, status) + ORDER BY created_at
EXPLAIN の Extra に Using filesort が表示されたら、インデックスの順序とソート順の不一致を疑ってください。
単一インデックス vs 複合インデックス
常に複合インデックスが良いわけではありません。
| 条件 | 推奨 |
|---|---|
| 単一カラムで検索することが多い | 単一インデックス |
| 複数カラムを組み合わせて検索 | 複合インデックス |
| カラムの組み合わせパターンが複数 | 複数の単一インデックス |
また、複合インデックスを作ると、その左端プレフィックスに対するインデックスは不要になります。
-- (user_id, status) があれば、(user_id) 単体のインデックスは不要
-- MySQL が自動的に (user_id) としても使ってくれる設計のポイント
複合インデックスを設計するときは、以下を考慮しましょう。
インデックスの列順は後から変更できますが、データ量が多いと再構築に時間がかかります。設計段階でクエリパターンを十分に検討しておくことが大切です。