TL

複合インデックスの列順序と左端プレフィックス則

複合インデックスが効くか効かないかは列の順序で決まります。左端プレフィックス則とスキップスキャンの内部動作を押さえれば、なぜその列順なのかを根拠を持って設計できます。

応用インデックス複合インデックスB+Treeクエリ最適化データベース最終更新: 2026-06-21
TL;DR要点だけ先に
  • 1.複合インデックスは列を連結した1本のキーで葉を並べる。先頭列から順に固定したぶんだけ範囲が絞れる。これが左端プレフィックス則の正体。
  • 2.等値述語の列を先に、範囲述語の列を後に置く。範囲列が来た時点で以降の列の整列は崩れ、後続列は絞り込みにもソートにも使えなくなる。
  • 3.先頭列を WHERE で指定しないクエリは原則インデックス全体を使えないが、先頭列の異なる値が少数なら DB はスキップスキャンで部分的に救済できる。

複合インデックスは「連結キー1本」である

複合インデックス(マルチカラムインデックス)とは、複数の列をまとめて1つのキーにした B+Tree です。(a, b, c) という複合インデックスは、a で並べ、a が同じものは b で並べ、b も同じものは c で並べる、という 辞書順(lexicographic order) で葉を整列させます。電話帳が「姓 → 名」の順で並ぶのと同じ構造です。

ここを誤解すると設計を誤ります。(a, b, c) は「a のインデックスと b のインデックスと c のインデックスの集合」ではありません。a・b・c を連結した1本の複合キーで葉が並んだ、たった1本の木 です。葉のキー比較は常に先頭列から始まり、先頭列で差がついた時点で残りの列は比較されません。

(a, b, c) インデックスの葉の並び(辞書順)
  (1, 'x', 10)
  (1, 'x', 30)
  (1, 'y', 5)
  (2, 'a', 99)
  (2, 'a', 100)
  ...
a で大分類、a 内で b、b 内で c。a を飛ばして b だけで並ぶことは絶対にない。

左端プレフィックス則

この並びから、インデックスが使える条件が一意に決まります。それが 左端プレフィックス則(leftmost prefix rule) です。複合インデックス (a, b, c) は、先頭の列から連続した接頭辞を WHERE で指定したときにだけ、絞り込みに使えます。

WHERE 条件(a,b,c) の利用範囲理由
a = ?a で絞り込み可先頭列が確定し範囲が連続する
a = ? AND b = ?a, b で絞り込み可連続した接頭辞が確定
a = ? AND b = ? AND c = ?全列で絞り込み可完全一致のプレフィックス
b = ? のみ原則使えない先頭 a が未確定で葉が連続しない
a = ? AND c = ?a だけ絞り込み、c は葉内フィルタb を飛ばすと c は連続しない

要点は「先頭列から連続して固定できたぶんだけ、目的の行が葉上で連続した1区間に集まる」ことです。a を固定すれば該当行は隣り合う葉に固まり、B+Tree は範囲の先頭を1回で見つけて葉リンクを順次たどれます(この仕組みは B+Tree の内部構造 を参照)。ところが b だけを指定すると、b='y' の行は a=1 の区画にも a=2 の区画にも散らばっており、葉上で連続しません。連続しない以上、範囲スキャンの起点を1つに定められず、インデックス全体を走査するしかなくなるのです。

試験・面接の定番

「(a, b, c) の複合インデックスがある。WHERE b = 10 AND c = 20 はインデックスを使えるか」——答えは「先頭列 a の指定が無いため、原則として効率的には使えない(後述のスキップスキャンが効く特殊ケースを除く)」。逆に WHERE a = 1 はインデックスを使える。左端から連続しているかどうかだけで判定する、というのが鉄則です。

等値が先、範囲は後 ——「範囲列で並びが終わる」

列順を決める最重要原則が、等値述語の列を先頭側に、範囲述語の列を末尾側に置く ことです。理由は B+Tree の整列の性質にあります。

ある列に範囲条件(>BETWEENLIKE 'abc%' など)が来ると、その列で複数の値にまたがります。複合キーの辞書順では、先行列が同値の範囲内でしか後続列は整列していない ため、先行列が複数値にわたった瞬間に、後続列の並びは全体としては崩れます。

-- (status, created_at) というインデックスを想定
SELECT * FROM orders
WHERE status = 'paid'              -- 等値:先頭で1値に固定
  AND created_at >= '2026-06-01';  -- 範囲:末尾で連続区間を取る

status='paid' で先頭が1値に固定されるので、その内部では created_at が昇順に連続して並びます。よって created_at の範囲は葉上の連続区間になり、範囲スキャンが綺麗に効きます。逆に列順を (created_at, status) にすると、created_at の範囲指定で先頭が複数日にまたがり、その各日の中でしか status は整列していないため、status='paid' での絞り込みはインデックスでは効かず、行ごとのフィルタに落ちます。

範囲列の「後ろ」の列は絞り込みに使えない

複合インデックスでは、範囲述語が当たった列より後ろの列は、もう絞り込みに使えません。(a, b, c)a = ? AND b > ? AND c = ? の場合、効くのは a の等値と b の範囲までで、c の等値はインデックス上では絞れず葉内フィルタになります(b が範囲なので c の並びが連続しないため)。この「範囲は最大1列、しかも最後に」という制約が列順設計の核心です。

ソート(ORDER BY)とカバリング

複合インデックスの並びは、絞り込みだけでなく ORDER BY のソート省略 にも直結します。インデックスの列順とソート列の順・方向が一致すれば、DB は葉を順にたどるだけで整列済みの行を得られ、明示的なソート処理(Sort ノード)を消せます。

-- (user_id, created_at) のインデックスがある
SELECT * FROM posts
WHERE user_id = 42
ORDER BY created_at DESC;   -- user_id 固定 → created_at 順は葉の並びそのもの

user_id=42 で先頭が固定され、その区画内は created_at 順に並ぶので、葉を降順にたどれば ORDER BY created_at DESC を追加コストなしで満たせます。なお SELECT する列がすべてインデックスに含まれる場合は、テーブル本体を読まずインデックスだけで応答が完結する カバリングインデックス(covering index / index-only scan) になり、ランダムな本体アクセスを省けます。これらが効くかどうかは実行計画で確認します(クエリ最適化、内部のコスト見積もりは オプティマイザの内部 を参照)。

スキップスキャン ——左端プレフィックス則の例外

「先頭列が WHERE に無いと使えない」は原則ですが、例外があります。スキップスキャン(skip scan / loose index scan) です。Oracle・MySQL 8.0(InnoDB)・PostgreSQL(18 以降の B+Tree)などが実装しています。

仕組みはこうです。先頭列 a の異なる値(distinct value)が少数しかない場合、DB は「a の各値ごとに、後続列の条件で内部的に小さな範囲探索を繰り返す」ことで、先頭列が指定されていなくてもインデックスを部分的に使えます。

(status, created_at) インデックスで status を指定しないクエリ
  WHERE created_at = '2026-06-01'
スキップスキャンの内部動作(status の distinct = paid, shipped, canceled の3値なら):
  status='paid'     の中で created_at='2026-06-01' を範囲探索
  status='shipped'  の中で created_at='2026-06-01' を範囲探索
  status='canceled' の中で created_at='2026-06-01' を範囲探索
→ 3回の小さな探索の結合。フルスキャンより速いことがある。

つまり、欠けている先頭列の値を オプティマイザが内部で1つずつ補い、それぞれについて通常のレンジスキャンを走らせて結果を束ねる、というのが本質です。

スキップスキャンが効く/効かない境界

スキップスキャンが有利なのは、先頭列のカーディナリティ(distinct 値の数)が小さいときだけです。status(数種類)なら数回の探索で済みますが、user_id(数百万種類)が先頭だと探索回数がフルスキャン並みに膨らみ、まず採用されません。あくまで「先頭列を指定し忘れた」「設計上どうしても先頭に置けない」ケースの救済策であり、これを当てにして列順を決めるべきではありません。理想は、よく使う等値条件の列を本当に先頭へ置くことです。

列順を決める実務ルール

以上を踏まえると、複合インデックスの列順は次の優先度で決めるのが定石です。

  1. 等値(=)で頻繁に絞る列を先頭へ。 左端プレフィックス則を満たし、最も多くのクエリで再利用できる。
  2. その中で選択性(カーディナリティ)が高い列を優先。 1回の絞り込みで候補行を強く減らせる。
  3. 範囲(>, BETWEEN, LIKE 'x%')で使う列は末尾に1つだけ。 範囲列より後ろは絞り込めないため。
  4. ORDER BY / GROUP BY の列順と方向を、範囲列の後ろに合わせる。 ソート処理を省けるなら大きい。

なお JOIN のキーも本質的には等値述語なので、結合に使う列は先頭側に置くと結合アルゴリズムが有利に働きます(各方式の挙動は JOIN アルゴリズム を参照)。列順は「どのクエリを速くしたいか」から逆算するものであり、(a, b)(b, a)まったく別物の道具 だと理解することが、過剰なインデックス乱立を防ぐ第一歩です。

データベース Article

複合インデックスの列順序と左端プレフィックス則を実務で読む

TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。

解決すること

インデックス

比較で見る軸

難易度: advanced / カテゴリ: データベース / タグ数: 5

導入後に効く点

等値述語の列を先に、範囲述語の列を後に置く。範囲列が来た時点で以降の列の整列は崩れ、後続列は絞り込みにもソートにも使えなくなる。

先に潰すリスク

用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。

数字・仕様の読み方
難易度
advanced
カテゴリ
データベース
タグ数
5

判断チェックリスト

  • 自社の用途が「インデックス / 複合インデックス」に近いか確認する。
  • 強みである「複合インデックスは列を連結した1本のキーで葉を並べる。先頭列から順に固定したぶんだけ範囲が絞れる。これが左端プレフィックス則の正体。」が本当に評価軸になるか確認する。
  • 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
  • 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
  • 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
  • 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。

次に確認する観点

インデックス複合インデックスB+Treeクエリ最適化データベースインデックス複合インデックスB+Tree