MySQL のインデックスが使われない原因と対処法

インデックスを作ったのに使われない。そんな経験はありませんか?インデックスが期待どおりに機能しない原因と、その対処法を解説します。

原因1: カラムに関数や演算を適用している

WHERE 句でカラムに関数を適用すると、インデックスは使われません。

インデックス無効

WHERE YEAR(created_at) = 2024

インデックス有効

WHERE created_at >= ‘2024-01-01’ AND created_at < ‘2025-01-01’

MySQL 8.0 では関数インデックスを作成できますが、通常はクエリを書き換えるほうが簡単です。

-- 関数インデックスの例(MySQL 8.0以降)
ALTER TABLE logs ADD INDEX idx_year ((YEAR(created_at)));

原因2: 暗黙の型変換が発生している

カラムの型と比較値の型が異なると、暗黙の型変換が発生してインデックスが効かなくなります。

-- phone_number が VARCHAR 型の場合
SELECT * FROM users WHERE phone_number = 09012345678;  -- 数値として比較→インデックス無効
SELECT * FROM users WHERE phone_number = '09012345678';  -- 文字列として比較→インデックス有効

文字列カラムには文字列リテラルで、数値カラムには数値リテラルで比較しましょう。

原因3: LIKE で前方一致以外を使っている

LIKE 演算子は、前方一致の場合のみインデックスが使われます。

インデックス有効

WHERE name LIKE ‘tanaka%’

インデックス無効

WHERE name LIKE ‘%tanaka’ や WHERE name LIKE ‘%tanaka%’

部分一致や後方一致が必要な場合は、FULLTEXT インデックスや外部の全文検索エンジンを検討してください。

原因4: OR 条件を使っている

OR で複数条件を結合すると、インデックスが使われないことがあります。

-- インデックスが効きにくい
SELECT * FROM products WHERE category_id = 1 OR price < 1000;

異なるカラムの OR 条件は、UNION に書き換えると改善することがあります。

SELECT * FROM products WHERE category_id = 1
UNION
SELECT * FROM products WHERE price < 1000;

原因5: NULL との比較

IS NULLIS NOT NULL は、MySQL のバージョンや設定によってインデックスの使用可否が変わります。

-- インデックスが使われないことがある
SELECT * FROM users WHERE deleted_at IS NULL;

NULL を多用するカラムでは、デフォルト値を設定して NULL を避ける設計も検討してください。

原因6: カーディナリティが低い

カーディナリティ(値の種類の数)が低いカラムのインデックスは、効果が薄いと判断されて使われないことがあります。

-- status が 'active' / 'inactive' の2種類しかない場合
SELECT * FROM users WHERE status = 'active';  -- インデックスより全件スキャンが速いと判断される可能性

オプティマイザは統計情報を基に判断します。テーブルの大部分が該当する条件では、インデックスを使わないほうが速いこともあるのです。

原因7: 複合インデックスの列順が合っていない

複合インデックスは、左端のカラムから順に使われます。

-- インデックス: (category_id, brand_id)
SELECT * FROM products WHERE brand_id = 5;  -- category_id がないのでインデックス無効
SELECT * FROM products WHERE category_id = 1;  -- 有効
SELECT * FROM products WHERE category_id = 1 AND brand_id = 5;  -- 有効

クエリで使う条件に合わせて、インデックスの列順を設計する必要があります。

インデックスが使われているか確認する方法

EXPLAIN の key 列を見れば、どのインデックスが使われたかわかります。

EXPLAIN SELECT * FROM users WHERE email = 'test@example.com';

key が NULL なら、インデックスは使われていません。possible_keys に候補があるのに key が NULL の場合は、オプティマイザが使わないと判断したということです。

強制的にインデックスを使わせる

どうしてもインデックスを使わせたい場合は、ヒント句を使えます。

SELECT * FROM users FORCE INDEX (idx_email) WHERE email = 'test@example.com';

ただし、オプティマイザの判断を上書きするため、状況が変わると逆効果になることもあります。使用は慎重に判断してください。

まとめ

カラムに関数や演算を適用していないか確認
型変換が発生していないかチェック
LIKE は前方一致のみインデックス有効
OR は UNION への書き換えを検討
複合インデックスの列順を確認

EXPLAIN を使って、インデックスが使われているか常に確認する習慣をつけましょう。