🎯 STEP 13: インデックス設計戦略
インデックスを適切に設計し、クエリパフォーマンスを劇的に改善しよう
- インデックスとは何か、どのように動作するか
- インデックスを貼るべきカラムの判断基準
- 複合インデックスの設計と順序の決め方
- カバリングインデックスの活用
- クエリパフォーマンス改善の実践テクニック
学習時間の目安: 2.5時間 | 前提知識: SQL基礎、テーブル設計の基本を理解していること
🎯 1. インデックスとは?
STEP 12からの続き:Part 3 実践設計編のスタート
STEP 7〜12では正規化について学びました。このSTEP 13からはPart 3「実践設計編」が始まります。正規化されたテーブルを実際に高速で動作させるための技術を学んでいきましょう。
まず最初に学ぶのはインデックス設計です。どんなに美しく正規化されたテーブルでも、インデックスがなければ検索性能は悲惨なことになります。
インデックスの基本
インデックス(Index)とは、データベースのデータに素早くアクセスするための仕組みです。本の索引(さくいん)と同じイメージです。
500ページある本から「データベース」という単語が書かれているページを探すとき:
索引なし:1ページ目から順番に全ページをめくって探す → 時間がかかる
索引あり:巻末の索引で「データベース」を探し、該当ページ(例:p.123, p.245)に直接飛ぶ → 超高速
データベースのインデックスも、まったく同じ原理です!
インデックスがない場合 vs ある場合
データベースは全ての行を1つずつチェックする必要があります。
インデックスを使って該当データに直接ジャンプできます。
インデックスの仕組み
ほとんどのデータベースはB-Tree(バランスツリー)というデータ構造を使っています:
- データがソートされた状態で木構造に保存される
- 二分探索のように、効率的に目的のデータを見つけられる
- 100万件のデータでも、約20回程度の比較で目的のデータに到達
- 挿入・削除時も、木構造を自動的にバランス調整する
🔍 2. インデックスを貼るべきカラムの判断基準
すべてのカラムにインデックスを貼れば良いわけではありません。適切なカラムにだけインデックスを貼ることが重要です。
インデックスを貼るべきカラム
- 主キー(Primary Key)
→ 自動的にインデックスが作成される - 外部キー(Foreign Key)
→ JOIN処理で頻繁に使われるため - WHERE句で頻繁に使うカラム
→ 検索条件として使われる - ORDER BY句で使うカラム
→ ソート処理が高速化される - GROUP BY句で使うカラム
→ 集計処理が高速化される - カーディナリティが高いカラム
→ 重複が少なく、値の種類が多い(例:メールアドレス、電話番号)
インデックスを貼るべきでないカラム
- カーディナリティが低いカラム
→ 値の種類が少ない(例:性別、ステータス(true/false)、都道府県) - ほとんど検索に使わないカラム
→ インデックス維持のコストが無駄 - 頻繁に更新されるカラム
→ 更新のたびにインデックスも更新が必要で、パフォーマンス低下 - 小さいテーブル(数千行以下)
→ フルスキャンでも十分高速
実例で理解する
インデックス設計の判断:
| カラム | インデックス | 理由 |
|---|---|---|
| user_id | ✅ 自動 | PRIMARY KEYは自動的にインデックスが作成される |
| ✅ 自動 | UNIQUE制約により自動的にインデックスが作成される | |
| username | ✅ 推奨 | ログイン時にusernameで検索することが多い |
| password_hash | ❌ 不要 | 検索に使わない |
| gender | ❌ 不要 | カーディナリティが低い(値が3種類程度) |
| is_active | ⚠️ 状況次第 | 頻繁に「アクティブユーザーのみ」検索するなら有効 |
| created_at | ✅ 推奨 | 「特定期間のユーザー」を検索することが多い |
| last_login_at | ✅ 推奨 | 「最近ログインしたユーザー」の検索に使う |
推奨インデックス設計:
🔧 3. 複合インデックスの設計
複合インデックス(Composite Index)とは、複数のカラムを組み合わせたインデックスです。
複合インデックスが必要なケース
インデックス設計の選択肢:
- 単一インデックス:category_id と price にそれぞれ別々のインデックス
- 複合インデックス:(category_id, price, created_at) の複合インデックス
複合インデックスの順序が重要
複合インデックス (A, B, C) は、以下のクエリで有効です:
- ✅ WHERE A = …
- ✅ WHERE A = … AND B = …
- ✅ WHERE A = … AND B = … AND C = …
- ❌ WHERE B = …
- ❌ WHERE C = …
- ❌ WHERE B = … AND C = …
つまり、インデックスは「左から順番に」使われます!
複合インデックスの順序の決め方
原則1:等価条件(=)を先に
原則2:カーディナリティが高いものを先に(等価条件が複数ある場合)
原則3:よく使う条件を先に
実例:複合インデックスの効果
シナリオ:100万件の products テーブルから検索
| インデックス設計 | 実行時間 | 読み取り行数 |
|---|---|---|
| インデックスなし | 3.2秒 | 1,000,000行 |
| category_id のみ | 0.8秒 | 50,000行 |
| price のみ | 1.5秒 | 200,000行 |
| (category_id, price) | 0.05秒 | 2,000行 |
| (category_id, price, created_at) | 0.01秒 | 20行 |
結論:適切な複合インデックスで320倍の高速化を実現!
⚡ 4. カバリングインデックス
カバリングインデックス(Covering Index)とは、クエリが必要とする全てのカラムを含むインデックスのことです。
カバリングインデックスの仕組み
通常のインデックス:
カバリングインデックス:
ポイント:カバリングインデックスを使うと、テーブル本体を読む必要がなくなるため、さらに高速化されます。
カバリングインデックスの設計例
- インデックスサイズが大きくなる(多くのカラムを含むため)
- 更新時のオーバーヘッドが増える
- 全てのクエリに対してカバリングインデックスを作るのは現実的ではない
- 頻繁に実行される重要なクエリにのみ適用する
🎯 5. インデックス設計のベストプラクティス
インデックス数の適正化
インデックスのデメリット:
- ストレージ使用量が増える:インデックスもディスク容量を消費する
- 書き込み(INSERT/UPDATE/DELETE)が遅くなる:データ変更のたびにインデックスも更新が必要
- メモリ使用量が増える:インデックスもメモリにキャッシュされる
目安:1つのテーブルに5〜10個以内のインデックスが適切。それ以上は慎重に検討。
インデックス設計チェックリスト
- ✅ このカラムはWHERE句で頻繁に使われるか?
- ✅ カーディナリティが十分高いか?(値の種類が多いか)
- ✅ テーブルのサイズは十分大きいか?(数万行以上)
- ✅ このインデックスでパフォーマンスが本当に改善されるか?(実測する)
- ✅ 既存のインデックスと重複していないか?
- ✅ 書き込み性能への影響は許容範囲か?
実践的なインデックス設計フロー
🛠️ 6. EXPLAIN でクエリパフォーマンスを確認
EXPLAINは、SQLの実行計画を確認するコマンドです。インデックスが使われているかを確認できます。
EXPLAIN の使い方
EXPLAIN の読み方
| 項目 | 説明 |
|---|---|
| type |
アクセス方法 ✅ const, eq_ref, ref = インデックス使用(高速) ❌ ALL = フルテーブルスキャン(遅い) |
| key |
使用されたインデックス名 NULL = インデックス未使用 |
| rows |
検査される推定行数 少ないほど良い |
| Extra |
追加情報 ✅ Using index = カバリングインデックス使用 ⚠️ Using filesort = ソート処理が必要 ⚠️ Using temporary = 一時テーブル使用 |
実例:インデックスあり vs なし
📝 7. インデックス設計のまとめ
- 主キー、外部キー、WHERE句で使うカラムにインデックスを貼る
- カーディナリティが高いカラムを優先する
- 複合インデックスの順序は重要(等価条件 → 範囲条件 → ソート条件)
- カバリングインデックスで更なる高速化
- EXPLAIN で実行計画を確認し、インデックスが使われているか検証
- インデックスは多すぎず、少なすぎず、適切な数を維持
- 継続的な監視と最適化が重要
インデックス設計は「推測ではなく測定」が鉄則です。
✅ EXPLAINで実行計画を確認
✅ 実行時間を測定
✅ インデックス作成前後で比較
✅ 本番環境に近いデータ量でテスト
「このカラムにインデックスを貼れば速くなるだろう」という推測だけで判断せず、必ず実測して効果を確認しましょう!
📝 練習問題
インデックスとは何か、自分の言葉で説明してください。
インデックスとは、データベースのデータに素早くアクセスするための仕組みです。本の索引(さくいん)のように、特定のデータを探すときに全体を順番に見ていく必要がなく、直接目的のデータにジャンプできます。
例えば、100万件のユーザーテーブルから特定のメールアドレスを探す場合、インデックスがなければ全100万件をチェックする必要がありますが、インデックスがあれば数十回の比較で目的のデータを見つけられます。
以下のusersテーブルで、インデックスを貼るべきカラムを選び、その理由を説明してください。
推奨インデックス:
理由:
- user_id: PRIMARY KEYなので自動的にインデックス作成済み
- email, username: ログインや検索で頻繁に使われる、カーディナリティが高い
- (prefecture, age): 複合インデックス。等価条件(prefecture)を先に、範囲条件(age)を後に
- (is_premium, last_login_at): WHERE条件とORDER BYの両方に対応
- password_hash: 検索に使わないのでインデックス不要
- gender: カーディナリティが低い(3種類程度)ので優先度低い
複合インデックス (A, B, C) は、どのクエリで有効ですか?全て答えてください。
インデックスが有効なクエリ:
- ✅ クエリ1: WHERE A = 1
→ インデックスの先頭カラムAを使用するので有効 - ❌ クエリ2: WHERE B = 2
→ 先頭カラムAがないため、インデックスは使われない - ✅ クエリ3: WHERE A = 1 AND B = 2
→ AとBの両方を使用するので有効 - ✅ クエリ4: WHERE A = 1 AND B = 2 AND C = 3
→ A、B、Cの全てを使用するので最も効率的 - ⚠️ クエリ5: WHERE A = 1 AND C = 3
→ Aは使われるが、Cは使われない(Bがスキップされるため) - ❌ クエリ6: WHERE B = 2 AND C = 3
→ 先頭カラムAがないため、インデックスは使われない
重要ポイント:
複合インデックスは「左から順番に」使われます。途中のカラムをスキップすると、それ以降のカラムはインデックスとして機能しません。
以下のクエリに最適な複合インデックスを設計してください。インデックスのカラム順序とその理由も説明してください。
カラム順序の理由:
- category_id(最初):等価条件(=)なので最優先
- stock(2番目):等価条件ではないが、範囲条件より絞り込み効果が高い
- price(3番目):範囲条件(BETWEEN)
- created_at(最後):ORDER BY用。インデックスがソート済みなので、ソート処理が不要になる
さらに効果を高めるには(カバリングインデックス):
SELECT対象のカラム(product_id, product_name)もインデックスに含めることで、テーブル本体へのアクセスが不要になり、さらに高速化されます。
以下の3つのクエリがあります。最小限のインデックス数で全てのクエリを最適化するには、どのようなインデックスを作成すべきですか?
推奨インデックス(2つ):
各インデックスの役割:
| クエリ | 使用インデックス | 理由 |
|---|---|---|
| クエリ1 | idx_orders_customer_status_date | customer_id で絞り込み、order_date でソート |
| クエリ2 | idx_orders_customer_status_date | customer_id と status の両方で絞り込み |
| クエリ3 | idx_orders_order_date | order_date の範囲検索 |
設計のポイント:
- 1つの複合インデックスで複数のクエリに対応:(customer_id, status, order_date) は、クエリ1(customer_id のみ)とクエリ2(customer_id + status)の両方で有効
- 頻度の低いクエリは別インデックス:クエリ3は customer_id を使わないため、別インデックスが必要
- インデックス数の最小化:3つのクエリに対して、2つのインデックスで対応
以下のEXPLAIN結果を見て、問題点を指摘し、改善策を提案してください。
問題点の指摘:
- 両テーブルでフルテーブルスキャン(type = ALL):インデックスが使われていない
- users テーブルで5万行、orders テーブルで20万行をスキャン:非常に非効率
- Using filesort:ソート処理が必要(ORDER BY用のインデックスがない)
- key = NULL:どちらのテーブルもインデックスが使われていない
改善策:
改善のポイント:
- ✅ users.created_at にインデックス:WHERE条件での絞り込みが高速化(50000行 → 10000行)
- ✅ (orders.user_id, orders.status) の複合インデックス:JOINとWHERE条件の両方で有効(200000行 → 50行程度)
- ✅ type が range と ref に改善:インデックスが使われるようになった
- ⚠️ filesort は残る:COUNT結果でのソートなので、インデックスでは解決できない(集計後のソート)
【総合問題】以下のテーブルとクエリに対して、最適なインデックス設計を行ってください。CREATE INDEX文を全て書いてください。
推奨インデックス設計:
各インデックスの役割:
| インデックス | 対象クエリ | 設計理由 |
|---|---|---|
| (is_published, created_at, post_id, title) | クエリ1 | カバリングインデックス。SELECT対象の全カラムを含むため、テーブル本体へのアクセス不要 |
| (user_id, created_at) | クエリ2, 5 | user_id で絞り込み、created_at でソート。クエリ5のCOUNTにも有効 |
| (category_id, is_published, view_count) | クエリ3 | category_id と is_published で絞り込み、view_count でソート |
| (category_id, created_at, post_id, title) | クエリ4 | category_id と created_at で絞り込み。post_id, title を含めてカバリングインデックス化 |
最適化のポイント:
- ✅ 頻度の高いクエリはカバリングインデックス:クエリ1とクエリ4
- ✅ 複数のクエリで共用できるインデックス:(user_id, created_at) はクエリ2と5で共用
- ✅ インデックスの順序を最適化:等価条件 → 範囲条件 → ソート条件の順
- ✅ 外部キーのインデックス:JOIN性能を考慮
- ⚠️ インデックス数は4〜5個:適切な範囲内で、書き込み性能への影響も考慮
❓ よくある質問
Q1: すべてのカラムにインデックスを貼れば、すべてのクエリが速くなりますか?
いいえ、逆効果になる可能性があります。インデックスにはデメリットもあります。書き込み(INSERT/UPDATE/DELETE)が遅くなる、ストレージ使用量が増える、メモリ使用量が増える、などです。適切なバランスが重要で、目安として1つのテーブルに5〜10個程度のインデックスが適切です。
Q2: カーディナリティとは何ですか?どのくらいあれば「高い」と言えますか?
カーディナリティとは、「値の種類の多さ」です。カーディナリティが高い例としては、メールアドレス(ほぼユニーク)、電話番号、注文IDなどがあります。低い例としては、性別(2〜3種類)、都道府県(47種類)、ステータス(true/false)などです。目安として、全体の10%以上が同じ値の場合、カーディナリティが低いと判断できます。
Q3: 複合インデックス (A, B) と、単一インデックス A, B を別々に作るのは、どう違いますか?
大きく違います。複合インデックス (A, B) は WHERE A = … AND B = … のクエリで非常に効率的で、WHERE A = … のクエリでも使えますが、WHERE B = … のクエリでは使えません。一方、単一インデックスA と B を別々に作った場合、それぞれのカラムでの検索には使えますが、WHERE A = … AND B = … のクエリでは効率が複合インデックスより劣ります。頻繁に一緒に使われるカラムは、複合インデックスにすることをおすすめします。
Q4: EXPLAINの結果で、type が ALL でも問題ない場合はありますか?
はい、以下の場合は問題ありません。テーブルのサイズが小さい(数千行以下)場合、テーブルの大部分(20%以上)を取得する場合、頻繁に実行されないクエリの場合です。重要なのは、頻繁に実行される重要なクエリでフルスキャンが発生していないかを確認することです。
Q5: インデックスを作成したのに、使われていません。なぜですか?
いくつかの理由が考えられます。カラムに関数を適用している場合(WHERE YEAR(created_at) = 2025)、暗黙的な型変換がある場合、LIKE ‘%keyword%’ のような前方一致以外の検索の場合、OR条件で複数のカラムを指定している場合、統計情報が古い場合などです。EXPLAINで確認し、どのインデックスが使われているか(または使われていないか)を確認しましょう。
Q6: 本番環境でインデックスを追加するとき、注意点はありますか?
はい、重要な注意点があります。インデックス作成中はテーブルがロックされる可能性があるため、オフピーク時間に実行すること、MySQL 5.6以降ではONLINE DDLを使用すること、ステージング環境でテストすること、ロールバック計画を準備しておくことが重要です。推奨コマンドは CREATE INDEX idx_name ON table(column) ALGORITHM=INPLACE, LOCK=NONE; です。
学習メモ
データベース設計・データモデリング - Step 13