インデックスが機能しない?パフォーマンス低下の主な原因
MySQLでの高速検索を支えるインデックスは、正しく使われなければ逆にパフォーマンスを損なうことがあります。たった一つの記述ミスで、クエリがミリ秒から数秒へと急減速することも珍しくありません。ここでは、実務でよく遭遇するインデックス非効率化のシナリオを10パターン紹介し、それぞれの回避方法をコード付きで解説します。
インデックスが効かない10の事例と解決策
1. OR条件によるインデックススキップ
SELECT * FROM users WHERE id = 100 OR score = 90;
問題点:score列にインデックスがない場合、ORの片方が非インデックスになると全体がフルスキャンされる可能性があります。
改善策:UNION ALLで分離して個別にインデックスを利用させる。
SELECT * FROM users WHERE id = 100
UNION ALL
SELECT * FROM users WHERE score = 90 AND id != 100;
2. カラムに対する関数適用
SELECT * FROM logs WHERE YEAR(log_time) = 2024;
問題点:関数がカラムに直接かかると、B+ツリー構造が無効化されます。
改善策:範囲検索に変換する。
SELECT * FROM logs WHERE log_time >= '2024-01-01' AND log_time < '2025-01-01';
3. 暗黙的な型変換
SELECT * FROM customers WHERE customer_code = 10001; -- customer_codeはVARCHAR
問題点:文字列と数値の比較でMySQLが内部的にCASTを挿入し、インデックスが使われなくなる。
改善策:データ型を一致させる。
SELECT * FROM customers WHERE customer_code = '10001';
4. LIKEの先頭ワイルドカード
SELECT * FROM articles WHERE title LIKE '%チューニング%';
問題点:B+ツリーは前方一致までしかサポートしないため、インデックスが無視される。
改善策:全文検索(FULLTEXT)を使用する。
CREATE FULLTEXT INDEX ft_title ON articles(title);
SELECT * FROM articles WHERE MATCH(title) AGAINST('チューニング');
5. 複合インデックスと最左一致則の違反
CREATE INDEX idx_name_age ON employees(name, age);
SELECT * FROM employees WHERE age = 30; -- インデックス未使用
問題点:複合インデックスは左端から順に使う必要があるため、age単体では利用不可。
改善策:クエリの順序を見直すか、必要な列に単独インデックスを追加。
CREATE INDEX idx_age ON employees(age);
6. 否定条件(NOT IN, NOT LIKE)の使用
SELECT * FROM orders WHERE status NOT IN (0, 1);
問題点:否定条件はインデックスの利点を活かせず、結果として全件スキャンになることが多い。
改善策:可能な限り肯定形に書き換える。
SELECT * FROM orders WHERE status IN (2, 3, 4);
7. 数式演算のカラムへの適用
SELECT * FROM products WHERE price * 1.1 > 1100;
問題点:計算式が含まれるとインデックスの直接参照ができなくなる。
改善策:式を移項して定数側に集約。
SELECT * FROM products WHERE price > 1000;
8. データの偏り(データスキュー)
CREATE INDEX idx_status ON tasks(status); -- status: 0(未処理), 1(完了)
SELECT * FROM tasks WHERE status = 0; -- 完了済みが90%なら、インデックス不使用の可能性
問題点:選択性が低く(特定値の出現率が高い)、インデックス経由より全表走査の方が高速と判断される。
改善策:複合インデックスで選択性を高める。
CREATE INDEX idx_status_priority ON tasks(status, priority);
9. 文字セットの不一致による結合失敗
SELECT * FROM orders o JOIN customers c ON o.customer_id = c.id
WHERE c.name = '田中'; -- orders: latin1, customers: utf8mb4 → 変換発生
問題点:文字セットが異なると暗黙のCONVERTが入り、インデックスが使われない。
改善策:両テーブルの文字セットを統一。
ALTER TABLE orders CONVERT TO CHARACTER SET utf8mb4;
10. SELECT * の過剰使用
SELECT * FROM employees WHERE department_id = 5;
問題点:インデックスがdepartment_idのみの場合、他の列を取得するために「リバースルックアップ(回表)」が必要になり遅くなる。
改善策:カバレッジインデックスを作成する。
CREATE INDEX cov_dept_name ON employees(department_id, name, email);
パフォーマンスチューニングの基本ステップ
- EXPLAINの活用:クエリ計画を確認し、typeがALLになっていないか、keyが適切に使われているかをチェック。
- プロファイリング:
SHOW PROFILEで各ステップの実行時間を分析。 - スロークエリログ:
slow_query_logを有効化し、長期的に遅いクエリを収集・改善。