インデックス(なぜ速くなるか)
インデックスは“本の巻末索引”。目的の行へ一直線でたどり着けるようにする仕組みで、検索を劇的に速くする代わりに、書き込みと容量のコストを払う。
- 1.インデックスは本の索引と同じ。全データを上から読む(全表スキャン)代わりに、目的の場所へ最短で飛べるようにする仕組み。
- 2.中身はたいてい B-tree(ソート済みの木構造)。だから等価検索も範囲検索もログ時間で速い。
- 3.万能ではない。読みは速くなるが書き込み(INSERT/UPDATE/DELETE)は遅くなり容量も食う。貼りすぎ・効かない書き方に注意。
なぜ速くなる? 全表スキャンとの対比
WHERE email = 'a@example.com' という検索を考えます。インデックスが無ければ、DB は先頭行から1行ずつ「これか?違う」を繰り返します。これが全表スキャン(フルスキャン)。100万行あれば最悪100万回の照合です。
一方インデックスがあると、ソート済みの索引をたどって目的の行の在りかだけを取り出し、本体へ直行します。比較回数は「全部」から「木の深さ(数回〜十数回)」へ激減します。
| 観点 | 全表スキャン | インデックススキャン |
|---|---|---|
| 読む量 | テーブル全行を順に読む | 索引をたどり、必要な行だけ読む |
| 計算量の目安 | O(N)(行数に比例) | O(log N)(木の深さに比例) |
| 得意な場面 | ほぼ全行が対象(集計など) | ごく一部の行を絞り込む検索 |
| 弱点 | 大きな表で遅い | 貼る・維持するコストがかかる |
インデックスが効くのは「全体のうちほんの一部だけがヒットする」とき。逆に、ほぼ全行が条件に当てはまる(例: WHERE is_active = true で大半が true)ような場合は、索引を経由して本体へ何度も飛ぶより、最初から全部まとめて読む全表スキャンの方が速いことがあります。実際オプティマイザは行数を見積もって、わざと索引を使わない判断をします。
中身は B-tree(バランス木)
多くの DB の既定インデックスは B-tree(厳密には B+tree) という木構造です。ポイントは2つ。
- 常にソート済み:キーが順番に並んでいる。だから「= の一致」だけでなく、
>・<・BETWEENのような範囲検索やORDER BYにも効きます。 - 木の高さが低く保たれる(バランス):データが増えても木は浅いまま自動調整され、どの行を探してもほぼ一定の少ない手数でたどり着けます。これが O(log N) の正体です。
-- email に B-tree インデックスを作成
CREATE INDEX idx_users_email ON users (email);
-- 等価検索も…
SELECT * FROM users WHERE email = 'a@example.com';
-- 範囲検索も索引が効く(ソート済みだから)
SELECT * FROM users WHERE created_at >= '2026-01-01';
キーをハッシュ化して格納するハッシュインデックスは、= の一致検索だけは非常に速い一方、順序情報を持たないため範囲検索や並べ替えには使えません。汎用性の高さから、まずは B-tree が既定になっている DB がほとんどです。
トレードオフ ── タダではない
インデックスは「読み取りを速くする代わりに、別のコストを払う」取引です。
| 項目 | インデックスを貼ると |
|---|---|
| 検索(SELECT) | 速くなる(狙った行に直行できる) |
| 書き込み(INSERT/UPDATE/DELETE) | 遅くなる(本体に加えて索引も更新が必要) |
| ディスク容量 | 増える(索引も実体を持つデータだから) |
| 運用 | 貼りすぎると更新が重く、無駄な索引が増える |
インデックスは検索を速くしますが、データを1行書き換えるたびに関連する索引すべてを更新します。だから索引を増やすほど INSERT/UPDATE/DELETE は重くなります。「念のため」で全列に貼ると、使われない索引が容量を食い、書き込みだけを遅くする“負債”になります。実際に検索条件・結合・並べ替えに使う列へ、必要な分だけ貼るのが原則です。
複合インデックスと「列の順序」
複数の列をまとめた 複合インデックス(マルチカラムインデックス) では、列を並べる順序が決定的に効きます。電話帳が「姓 → 名」の順に並んでいるのと同じで、前方の列から順に使えるという性質(左端プレフィックスの原則)があります。
-- (last_name, first_name) の順で作成
CREATE INDEX idx_name ON users (last_name, first_name);
| 検索条件 | 上の (last_name, first_name) 索引は… |
|---|---|
| last_name = '佐藤' AND first_name = '太郎' | 効く(両方を先頭から使える) |
| last_name = '佐藤' | 効く(先頭列だけでも使える) |
| first_name = '太郎'(last_name 指定なし) | 効きにくい(先頭列を飛ばしているため) |
電話帳で「名(太郎)」だけを手がかりに探すと結局めくる羽目になるのと同じ理屈です。よく単独で絞り込む列を先頭に置くのがコツです。
インデックスが「効かない」よくあるケース
索引を貼ったのに使われない、という落とし穴は典型パターンが決まっています。
インデックスは「列の素の値」が並んだもの。条件式の中で列に関数や演算をかけると、ソート済みの並びと対応が取れず、索引を使えなくなります。
-- ✕ 列を加工 → 多くの DB で索引が効かない
SELECT * FROM users WHERE LOWER(email) = 'a@example.com';
SELECT * FROM orders WHERE YEAR(created_at) = 2026;
-- ○ 列はそのまま、右辺側を調整する
SELECT * FROM orders
WHERE created_at >= '2026-01-01' AND created_at < '2027-01-01';
LOWER(email) のように加工した結果で検索したいなら、その**式そのものに対するインデックス(関数インデックス/式インデックス)**を別途用意します。
そのほか効きにくくなる代表例:
- 前方一致でない
LIKE:LIKE 'abc%'(前方一致)は B-tree が効きますが、LIKE '%abc'のように先頭にワイルドカードが来ると、並び順を手がかりにできず効きません。 - ヒット率が高すぎる条件:前述のとおり、ほぼ全行が当てはまるなら全表スキャンが選ばれます。
- データ量が小さい:数十行のテーブルは、索引を経由するより全部読んだ方が速く、そもそも使われないことがあります。
索引が本当に使われているかは想像で判断せず、EXPLAIN(実行計画) で確認します。Seq Scan(全表スキャン)か Index Scan かが表示されるので、狙った索引が効いているかを必ず実物で検証しましょう。
まとめ
- インデックス=本の索引。全表スキャン O(N) を、索引経由の O(log N) に変えて検索を速くする。
- 実体はソート済みの B-tree。だから等価検索だけでなく範囲検索・並べ替えにも効く。
- ただし読みは速く・書きは遅く・容量は増えるトレードオフ。使う列に絞って貼る。
- 複合インデックスは列順が命。加工した列・先頭ワイルドカード
LIKEでは効かない。EXPLAINで確認する。
絞り込みの速さは JOIN や トランザクション と並んで DB 性能の土台です。索引が前提とする整列の感覚は 正規化 を学ぶとさらに腑に落ちます。
データベース Article
インデックス(なぜ速くなるか)を実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
データベース
比較で見る軸
難易度: intermediate / カテゴリ: データベース / タグ数: 4
導入後に効く点
中身はたいてい B-tree(ソート済みの木構造)。だから等価検索も範囲検索もログ時間で速い。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- intermediate
- カテゴリ
- データベース
- タグ数
- 4
判断チェックリスト
- 自社の用途が「データベース / インデックス」に近いか確認する。
- 強みである「インデックスは本の索引と同じ。全データを上から読む(全表スキャン)代わりに、目的の場所へ最短で飛べるようにする仕組み。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。