Day6 後半のゴール
「“インデックスあり/なし”で何が変わるかを、具体的なSQLで説明できるようになる」
前半では、
インデックス=目次
複合インデックス=複数カラムの組み合わせの目次
というところまで行きました。
後半のゴールは、
インデックスがないときに何が起きているかをイメージできる
インデックスを張ると、どんなクエリが速くなるかを説明できる
複合インデックスの「効くパターン・効かないパターン」を具体例で語れる
ここまで行くと、「なんとなくCREATE INDEX」から卒業できます。
例題テーブル:大量ユーザーを想定した users テーブル
「name で検索するときにインデックスが効くイメージを持つ」
まずはシンプルな users テーブルを使います。
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50) NOT NULL,
email VARCHAR(255) NOT NULL
) ENGINE=InnoDB;
SQLここには、すでに何万件もユーザーが入っていると想像してください。
(実際にそこまで入れなくても、イメージできればOKです)
やりたいことはこうです。
名前が「山田太郎」のユーザーを探したい
SQL はこうなります。
SELECT *
FROM users
WHERE name = '山田太郎';
SQLこのとき、name にインデックスがないと、
MySQL は users の全行を1件ずつチェックします。
100万行あれば、100万回「name が ‘山田太郎’ か?」を確認します。
これが「インデックスなし」の世界です。
CREATE INDEX を張ってみる
「同じクエリでも、MySQL の“探し方”が変わる」
次に、name にインデックスを張ります。
CREATE INDEX idx_users_name
ON users (name);
SQLこれで、MySQL は「name だけを集めた目次」を内部に持つようになります。
同じクエリをもう一度見てみます。
SELECT *
FROM users
WHERE name = '山田太郎';
SQL今度は、MySQL はこう動きます。
name のインデックス(目次)を見て、「’山田太郎’ がどの行にいるか」を特定する
→ その行だけをテーブル本体から取りに行く
つまり、「全部なめる」のではなく、
「目次からピンポイントで取りに行く」動きに変わります。
実際の速度差はデータ量や環境によりますが、
10万行、100万行と増えるほど、
インデックスの有無で体感が変わってきます。
インデックスが効きやすい条件・効きにくい条件
「“そのままの形で WHERE に出てくるか”がポイント」
インデックスは万能ではありません。
効きやすい条件と、効きにくい条件があります。
例えば、次のようなクエリはインデックスが効きやすいです。
SELECT *
FROM users
WHERE name = '山田太郎';
SQLname をそのまま条件に使っているからです。
一方で、こう書くと効きにくくなります。
SELECT *
FROM users
WHERE LEFT(name, 2) = '山田';
SQLname にインデックスがあっても、
関数(LEFT)で加工してしまうと、
「インデックスの値」と一致しなくなり、
インデックスを使えないことが多くなります。
Day6 の段階では、
インデックスを張ったカラムは、なるべく素直に WHERE に書く
という感覚だけ持っておけば十分です。
例題:ログイン履歴テーブルで複合インデックスを試す
「user_id+logged_at の組み合わせに意味があるケース」
次は、複合インデックスの話に進みます。
ログイン履歴テーブルをもう一度使います。
CREATE TABLE login_logs (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
logged_at DATETIME NOT NULL,
ip VARCHAR(45) NOT NULL
) ENGINE=InnoDB;
SQLよくあるクエリはこれです。
特定ユーザーのログイン履歴を、新しい順に50件だけ欲しい
SELECT *
FROM login_logs
WHERE user_id = 123
ORDER BY logged_at DESC
LIMIT 50;
SQLこのクエリを速くしたいとき、
複合インデックスが効いてきます。
複合インデックスを張る
「“絞り込みに使う順番”でカラムを並べる」
このクエリに合わせて、こういうインデックスを張ります。
CREATE INDEX idx_login_logs_user_date
ON login_logs (user_id, logged_at);
SQLこのインデックスは、
まず user_id で絞り込む
その中で logged_at の順に並んでいる
という構造になっています。
先ほどのクエリは、
WHERE user_id = 123
ORDER BY logged_at DESC
なので、
このインデックスと相性がとても良いです。
MySQL は、
インデックスの中から user_id=123 の範囲を素早く見つける
→ その範囲を logged_at の順にたどりながら、上から50件だけ取る
という動きができます。
複合インデックスの“効くパターン”
「先頭カラムを条件に使っているかどうかを見る」
さっきのインデックス:
ON login_logs (user_id, logged_at)
SQLが効く代表的なパターンは、次のようなクエリです。
SELECT *
FROM login_logs
WHERE user_id = 123;
SQLSELECT *
FROM login_logs
WHERE user_id = 123
AND logged_at >= '2025-05-01';
SQLSELECT *
FROM login_logs
WHERE user_id = 123
ORDER BY logged_at DESC
LIMIT 50;
SQL共通しているのは、
WHERE 句に user_id が含まれている
→ 先頭カラムから絞り込みを始められる
という点です。
「左から順に」というルールを思い出してください。
複合インデックスは、先頭カラムを使ってくれるクエリに強いです。
複合インデックスの“効きにくいパターン”
「先頭カラムを無視した条件には弱い」
逆に、こういうクエリには効きにくくなります。
SELECT *
FROM login_logs
WHERE logged_at >= '2025-05-01';
SQLこのクエリは、user_id を条件に使っていません。
インデックスは (user_id, logged_at) の順なので、
「user_id から探し始める」前提になっています。
先頭カラムを使わない条件だと、
MySQL はこの複合インデックスをうまく活用できず、
結局フルスキャンに近い動きになることがあります。
ここでのポイントは、
複合インデックスを作るときは、
「どのカラムから絞り込むクエリが多いか」をよく考える
ということです。
単一インデックスを2つ作る vs 複合インデックス1つ
「“セットで使う”なら複合インデックスが強い」
よくある疑問がこれです。
user_id にインデックス
logged_at にインデックス
を別々に張るのと、
(user_id, logged_at) の複合インデックスを1つ張るのは、
何が違うのか?
結論だけ言うと、
「user_id で絞って logged_at で並べる」クエリに対しては、
複合インデックスの方が圧倒的に有利です。
単一インデックスを2つ作っても、
MySQL は「2つのインデックスをうまく合体させてくれる」わけではありません。
複合インデックスは、
「user_id で絞り込んだ結果が、すでに logged_at 順に並んでいる」
という状態を作ってくれるので、
WHERE user_id = ?
ORDER BY logged_at
というクエリに対して、
インデックスだけでほぼ完結できることが多くなります。
インデックスを張りすぎるとどうなるか
「読み取りは速くなるが、書き込みコストが増える」
ここまで聞くと、
「じゃあ、よく使いそうなカラム全部にインデックス張ればいいじゃん」
と思うかもしれません。
でも、インデックスにはデメリットもあります。
INSERT / UPDATE / DELETE のたびに、
インデックスも更新しなければならない
つまり、
インデックスが多いほど、書き込みが重くなる
という側面があります。
Day6 の段階では、
本当に検索に使うカラムだけにインデックスを張る
「とりあえず全部」はやらない
という感覚を持っておけば十分です。
セキュリティ・運用の視点から見たインデックス
「“遅いからログを削る”みたいな雑な運用を防ぐ」
インデックスは性能の話に見えますが、
実はセキュリティや運用にも影響します。
ログテーブルが重くてアプリが遅い
→ 「古いログを全部消そう」となりがち
インデックスを適切に張っておけば、
必要な範囲だけを素早く取れる
→ ログを長期間残しても、実用的な速度を保てる
結果として、
監査ログやアクセスログを十分な期間残せる
→ 不正アクセスや障害調査に役立つ
という形で、
「ちゃんとした運用」を支える土台になります。
Day6 では、
インデックス設計は“快適さ”だけでなく、“守り”にも効く
という視点を、少し頭の片隅に置いておいてください。
Day6 後半のまとめ
単一インデックスを張ると、WHERE でそのカラムを素直に条件に使うクエリ(WHERE name = '山田太郎' など)が、フルスキャンではなく「目次からピンポイントで探す」動きに変わり、データ量が増えるほど効果が大きくなる。
複合インデックスは「(user_id, logged_at)」のように複数カラムをセットにした目次で、「WHERE user_id = ? ORDER BY logged_at」のようなクエリに強いが、「左から順にしか効かない」ため、先頭カラムを条件に使わないクエリ(WHERE logged_at >= ... だけなど)には効果が薄い。
単一インデックスを2つ作るだけでは、「user_id で絞って logged_at で並べる」クエリを十分に最適化できず、そのような“セットで使う”条件が多い場合は、複合インデックスを1つ用意した方が実戦的に強い。
インデックスは読み取りを速くする一方で、INSERT / UPDATE / DELETE のたびに更新コストがかかるため、「よく検索・絞り込みに使うカラムだけに張る」「なんとなく全部には張らない」というバランス感覚が重要になる。
ここまで理解できていれば、
「とりあえずCREATE INDEX」ではなく、
「このクエリを速くしたいから、この順番で複合インデックスを張る」と
意図を持って設計できる状態にかなり近づいています。
