インデックスを貼ったのに、あまり速くならい、むしろ更新が遅くなった。
MySQLを触っていると、こういった経験をした人も少なくはないでしょう。
インデックスは魔法ではなく、効く場面がかなりはっきりしています。
インデックスが効く場面を抑えることで、ある程度は再現性ををもって性能改善できます。
インデックスの基本と確認方法
インデックスは「探しやすい並び」
インデックスは、ざっくり言うと「探しやすい並び」です。
これを一番イメージしやすいのが 国語辞典 です。
辞書で「りんご」を調べるとき、最初のページから順に読んで探しません。
あいうえお順に並んでいるため、だいたい「り」のあたりを開けばすぐ見つかります。
この「あいうえお順に並んでいる状態」が、データベースでいうインデックスに近いです。
テーブルを辞書に例えると、こうなります。
- テーブル本体:単語がバラバラに書かれた巨大な紙束
- インデックス:単語を「あいうえお順」に並べた目次
インデックスがない場合、MySQLは目的のデータを見つけるために、基本的に先頭から順に探します。
これが フルスキャン です。辞書を最初から読み上げるようなものなので、件数が増えるほど遅くなります。
一方、インデックスがある場合は、「並び」を使って近い場所に一気に飛べます。
だから、条件に合うデータを見つけるまでの手間が大きく減ります。
ただし、辞書でも「英単語を日本語のページで探す」と見つからないのと同じで、
探し方が並びと噛み合っていないとインデックスは効きません。
インデックスが効く場合と効かない場合の差は、ここで決まります。
逆に遅くなるパターン
インデックスは貼れば貼るほど良いわけではありません。むしろ、貼りすぎると別のところでコストが発生します。
- 更新(INSERT / UPDATE / DELETE)が多いテーブルにインデックスを貼りすぎる
更新のたびにインデックスも更新されるため、書き込みの効率が悪くなります。
- 値の種類が少ない列に単体インデックスを貼る
性別フラグのように「ほとんど同じ値」だと、絞り込み効果が弱く、使われない可能性が高いです。
- 関数をかけた検索や、インデックスが効きにくい条件で使っている
WHERE DATE(created_at) = ... や LIKE '%xxx' など。
インデックスは並びの目次を用意する機能なので、使われる機会が少ない目次を用意しても効果は薄いです。
EXPLAINの基本(key / rows / type)
インデックス設計で迷った際に参考になるのがEXPLAINです。
初心者が最初に見るべきポイントは3つだけで十分です。
key:実際に使われたインデックス名
⇒ここがNULLなら、基本的にインデックスを使っていません。rows:読みそうな行数の見積もり
⇒ざっくりでいいので「想定より大きすぎないか」を見ます。type:アクセスの種類(大雑把に“良い/微妙”の目安)
⇒初学者の段階では、ALLが出ていたら「全表走査っぽい」と理解できれば十分です。
例として以下のように接頭にEXPLAINを指定して使用します。
EXPLAIN
SELECT *
FROM users
WHERE email = 'a@example.com';EXPLAINは完璧な答えを出す道具ではありませんが、「効いてる/効いてない」を判断するには十分役に立ちます。
WHERE / JOIN / ORDERで効くインデックス
ここからは、インデックスが効きやすい場面を3つに分けて整理します。
WHEREで効く
まず一番分かりやすいのがWHEREです。よくあるパターンは2つです。
1つ目は 等価検索(=) です。
SELECT *
FROM users
WHERE email = 'a@example.com';こういう「一致する行を探す」検索は、インデックスと相性が良いです。
とくに email のように値が分散している列は効きやすいです。
2つ目は 範囲検索(>, BETWEEN) です。
SELECT *
FROM orders
WHERE ordered_at >= '2026-01-01' AND ordered_at < '2026-02-01';この場合もインデックスは使えますが、範囲が広いほど読む行も増えます。
つまり「範囲検索なら常に速い」ではなく、「狭い範囲ほど効きやすい」です。
次に、実務で重要になりやすいのが 複合インデックスです。
CREATE INDEX idx_orders_customer_ordered
ON orders(customer_id, ordered_at);複合インデックスは、基本的に 左から順に 効きます。たとえば上の例なら、
customer_id = ?は効きやすいcustomer_id = ? AND ordered_at >= ?も効きやすいordered_at >= ?だけだと効きにくい
複合インデックスで失敗しやすいのは、順番を雰囲気で決めてしまうことです。
順番は「よく使うWHERE条件の並び」に合わせるのが基本です。
JOINで効く
JOINしたクエリが遅いときにまず見るべきなのは、結合に使っている列にインデックスがあるかです。
例として、ordersとorder_itemsを結合した場合を考えます。
SELECT *
FROM orders o
JOIN order_items i ON i.order_id = o.id
WHERE o.customer_id = 123;このとき、次のインデックスが候補になります。
orders.id(主キーなら通常ある)order_items.order_id(外部キー側、これが無いとJOINが重くなりやすい)orders.customer_id(WHEREで絞るなら)
JOINは、片方の表(駆動表)から候補行を出して、もう片方を探しにいくイメージです。
探しにいく側の列にインデックスが無いと、そのたびに全体をなめることになり、遅くなります。
初学者の段階では「JOINしている列にインデックスがあるか」を最優先で見れば十分です。
ORDERで効く
ORDER BYが遅いのは、MySQLが「並び替え」をする必要があるからです。並び替えは件数が増えるほど重くなります。
たとえば次のSQLは、条件で絞った後に並び替えます。
SELECT *
FROM orders
WHERE customer_id = 123
ORDER BY ordered_at DESC;このとき、次のような複合インデックスがあると、並び替えをうまく省略できることがあります。
CREATE INDEX idx_orders_customer_ordered
ON orders(customer_id, ordered_at);ポイントは、WHEREで絞る列とORDER BYの列が、インデックスの並びと噛み合っていることです。
噛み合うと、すでに並んでいる状態から取り出せるため、余計なソートが減ります。
ORDER BYにインデックスが効くかは条件があるので、まずはEXPLAINで確認するのが安全です。
迷ったときの設計指針
まず単体インデックス→必要なら複合へ
最初から複合インデックスを増やしすぎると、管理が破綻しやすいです。
基本の順番はシンプルにしておくと回ります。
- まず単体インデックスで明確に効かせたい箇所を押さえる
- それでも「WHERE+ORDER」や「複数条件が定番」なら複合にする
複合インデックスは強いですが、作り方を間違えると使われないまま更新コストだけ増えることになりやすいです。
複合インデックスのよくある失敗
複合インデックスでありがちな失敗は次の通りです。
- よく使う検索条件と順番がズレている
左から効くので、順番がズレると効きません。 - そもそもそのSQLが頻繁に使われていない
“たまに使う管理画面”のために重いインデックスを増やすと、全体の実行時間が増加します。 - 条件がインデックスを使いにくい形になっている
例:先頭ワイルドカードのLIKE、関数をかけたWHEREなど。
追加前に考えること
最後に、インデックス追加前に一度だけ考えておくと事故が減ります。
- そのSQLはどれくらいの頻度で走るか
- 遅い原因はインデックス不足か、それとも取得しすぎか
- そのテーブルは更新が多いか(更新が多いほどインデックス追加の副作用が出る)
まとめ
MySQLのインデックスは、難しい理論よりも「効く場面」を押さえるのが近道です。
- WHEREは、等価検索と範囲検索が基本。複合インデックスは左から効く
- JOINは、結合キー(とくに外部キー側)にインデックスがあるかが重要
- ORDER BYは、WHERE+ORDERの組み合わせでインデックスが効くことがある
そして、迷ったらEXPLAINで確認します。最初は key / rows / type の3点を見るだけで十分です。
効いていないなら、貼る場所かSQLの形が噛み合っていない可能性が高いです。


コメント