MySQL | SQLite経験者向け、30日で習得するMySQL:差分理解 - Day6 インデックス基礎

SQL MySQL
スポンサーリンク

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 = '山田太郎';
SQL

name をそのまま条件に使っているからです。

一方で、こう書くと効きにくくなります。

SELECT *
FROM users
WHERE LEFT(name, 2) = '山田';
SQL

name にインデックスがあっても、
関数(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;
SQL
SELECT *
FROM login_logs
WHERE user_id = 123
  AND logged_at >= '2025-05-01';
SQL
SELECT *
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」ではなく、
「このクエリを速くしたいから、この順番で複合インデックスを張る」と
意図を持って設計できる状態にかなり近づいています。

タイトルとURLをコピーしました