データベースインデックス
検索クエリの高速化のためにデータベースが維持する補助的なデータ構造
データベースインデックスとは
データベースインデックスは、テーブルの特定カラムに対する検索を高速化する補助的なデータ構造である。本の索引と同じ原理で、全ページを読む代わりに索引から目的のページを直接見つける。インデックスがなければ、DB はテーブル全体をスキャン (Full Table Scan) する必要がある。
インデックスの効果
インデックスの効果の例を示す。
-- インデックスなし: 100 万行を全スキャン → 行幅とキャッシュ状態次第で数百ミリ秒〜数秒
SELECT * FROM orders WHERE user_id = 'user-123';
-- user_id にインデックスあり: B-Tree で直接検索 → ミリ秒
CREATE INDEX idx_orders_user_id ON orders (user_id);
インデックスの種類
インデックスの種類を以下にまとめる (PostgreSQL の場合)。
| 種類 | データ構造 | 用途 |
|---|---|---|
| B-Tree | 平衡木 | 等値検索、範囲検索 (デフォルト) |
| Hash | ハッシュテーブル | 等値検索のみ (範囲検索不可) |
| GIN | 転置インデックス | 全文検索、配列、JSONB |
| GiST | 汎用検索木 | 地理空間データ、範囲型 |
このほか BRIN (値が物理順に並ぶ巨大なテーブル向け) と SP-GiST もある。GIN・GiST は PostgreSQL 固有の名前で、MySQL では全文検索インデックスと SPATIAL インデックスが近い役割を担う。
DynamoDB のインデックス
DynamoDB ではテーブル作成時にプライマリキー (パーティションキー + ソートキー) を定義し、追加のアクセスパターンには GSI (Global Secondary Index) を使う。
複合インデックス
複数カラムを組み合わせたインデックス。カラムの順序が重要だ。
-- (user_id, created_at) の複合インデックス
CREATE INDEX idx_orders_user_date ON orders (user_id, created_at);
-- ✅ 効く: user_id で検索
SELECT * FROM orders WHERE user_id = 'user-123';
-- ✅ 効く: user_id + created_at で検索
SELECT * FROM orders WHERE user_id = 'user-123' AND created_at > '2026-01-01';
-- ⚠ 走査範囲を絞れない: created_at だけで検索 (先頭カラムに条件がない)
SELECT * FROM orders WHERE created_at > '2026-01-01';
3 番目が遅いのは「インデックスが存在しないのと同じ」だからではない。等値条件が並ぶ先頭カラムと、そこで最初に等値でなくなったカラムの範囲条件までが走査範囲を絞る材料になり、それより右のカラムの条件は絞り込みに寄与しない。先頭カラムの条件が無ければ絞る材料がゼロなので、インデックス全体を走査することになり、PostgreSQL のプランナーは多くの場合テーブルの順次スキャンを選ぶ。MySQL は 8.0.13 で Skip Scan を導入し、先頭カラムの値ごとにレンジスキャンを繰り返してこの形を救えるようになったが、単一テーブルであること、参照カラムがすべてインデックスに含まれること、後続カラムに範囲条件があることなどの条件が付く。救済策に頼らず、絞り込みに使いたいカラムを左から順に並べるのが原則だ。
インデックスのトレードオフ
インデックスのトレードオフを以下に整理する。
| メリット | デメリット |
|---|---|
| SELECT が高速化 | INSERT/UPDATE/DELETE が遅くなる |
| ソートが高速化 | ストレージを消費する |
| 一意制約の重複チェックも速くなる | インデックスの選択を誤ると効果なし |
インデックスは「読み取りを速くする代わりに、書き込みを遅くする」トレードオフだ。書き込みが多いテーブルにインデックスを大量に作ると、書き込みパフォーマンスが低下する。
EXPLAIN で実行計画を確認
EXPLAIN で実行計画を確認の例を示す。
EXPLAIN ANALYZE SELECT * FROM orders WHERE user_id = 'user-123';
-- Index Scan using idx_orders_user_id on orders (cost=0.42..8.44 rows=1 width=124)
-- (actual time=0.031..0.033 rows=1 loops=1)
-- Index Cond: (user_id = 'user-123'::text)
-- Planning Time: 0.121 ms
-- Execution Time: 0.052 ms
Seq Scan (全スキャン) が表示されたら、インデックスが使われていない。
現場での応用を知るには関連書籍も役立つ。
この記事は役に立ちましたか?