はじめに
「最近DBが重い」「このクエリなんで遅いんだろう」——そう思ったとき、どこから手を付ければいいか迷うことがあります。
この記事では、MySQL のパフォーマンス問題を調査するときに最初に確認すべき5つのポイントを、実際に使えるSQLとコマンド付きで紹介します。
難しい理論より「まずこれを実行する」を重視して書きました。
確認する順番
① EXPLAIN でクエリを解析する
② インデックスが使われているか確認する
③ スロークエリログを有効にする
④ SELECT * を使っていないか見直す
⑤ N+1 クエリが発生していないか確認する
① EXPLAIN でクエリを解析する
遅いクエリの前に EXPLAIN を付けるだけで、MySQLがどのようにそのクエリを実行するかを確認できます。
EXPLAIN SELECT * FROM orders WHERE user_id = 123;
注目するカラム
| カラム | 確認ポイント |
|---|---|
type |
ALL(フルスキャン)が出たら要注意 |
key |
使用されているインデックス名。NULLならインデックス未使用 |
rows |
走査する推定行数。多いほど重い |
Extra |
Using filesort や Using temporary が出たら改善余地あり |
悪い例(type: ALL)
+----+-------------+--------+------+---------------+------+---------+------+--------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------+------+---------------+------+---------+------+--------+-------------+
| 1 | SIMPLE | orders | ALL | NULL | NULL | NULL | NULL | 100000 | Using where |
+----+-------------+--------+------+---------------+------+---------+------+--------+-------------+
100,000行を全部スキャンしています。これは典型的な「インデックス不足」のサインです。
② インデックスが使われているか確認する
テーブルのインデックス一覧を確認
SHOW INDEX FROM orders;
WHERE句のカラムにインデックスがない場合は追加する
-- インデックスを追加
ALTER TABLE orders ADD INDEX idx_user_id (user_id);
-- 追加後にEXPLAINで確認
EXPLAIN SELECT * FROM orders WHERE user_id = 123;
追加後に type: ref、key: idx_user_id と表示されれば成功です。
複合インデックスの順番に注意
-- WHERE user_id = ? AND status = ? のクエリに対して
-- ◎ 良い:よく絞り込むカラムを先頭に
ALTER TABLE orders ADD INDEX idx_user_status (user_id, status);
-- △ 悪い:順番が逆だと効果が薄い場合がある
ALTER TABLE orders ADD INDEX idx_status_user (status, user_id);
③ スロークエリログを有効にする
「どのクエリが遅いか分からない」という場合は、スロークエリログを有効にして記録します。
現在の設定を確認
SHOW VARIABLES LIKE 'slow_query%';
SHOW VARIABLES LIKE 'long_query_time';
一時的に有効化する(再起動不要)
-- スロークエリログをON
SET GLOBAL slow_query_log = 'ON';
-- 1秒以上かかったクエリを記録
SET GLOBAL long_query_time = 1;
-- ログファイルのパスを確認
SHOW VARIABLES LIKE 'slow_query_log_file';
my.cnf に書いて永続化する場合
[mysqld]
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 1
log_queries_not_using_indexes = 1 # インデックス未使用クエリも記録
mysqldumpslow でログを集計する
# 実行時間が長い順にTop10を表示
mysqldumpslow -s t -t 10 /var/log/mysql/slow.log
④ SELECT * を使っていないか見直す
SELECT * は手軽ですが、不要なカラムまで取得するためデータ転送量が増えます。特にTEXT型やBLOB型のカラムがある場合は影響が大きいです。
-- ❌ 悪い例:全カラム取得
SELECT * FROM products WHERE category_id = 5;
-- ✅ 良い例:必要なカラムだけ取得
SELECT id, name, price FROM products WHERE category_id = 5;
また、SELECT * はインデックスオンリースキャン(カバリングインデックス)が効かなくなるため、パフォーマンス上も不利です。
⑤ N+1 クエリが発生していないか確認する
N+1問題は、アプリケーション側で1回のクエリの結果に対してN回のクエリを発行してしまう問題です。DBのログや SHOW PROCESSLIST では気づきにくいため注意が必要です。
典型的なN+1の例(PHP)
// ❌ N+1が発生するコード
$users = $db->query("SELECT id, name FROM users LIMIT 100");
foreach ($users as $user) {
// ユーザーごとに1クエリ発行 → 合計101クエリ!
$orders = $db->query("SELECT * FROM orders WHERE user_id = {$user['id']}");
}
JOINで1回にまとめる
-- ✅ 1回のクエリで取得
SELECT
u.id,
u.name,
COUNT(o.id) AS order_count
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
GROUP BY u.id, u.name
LIMIT 100;
現在の接続・クエリ状況を確認
-- 実行中のクエリ一覧
SHOW FULL PROCESSLIST;
-- 長時間実行中のクエリ(10秒以上)
SELECT * FROM information_schema.processlist
WHERE command != 'Sleep' AND time > 10
ORDER BY time DESC;
まとめ
MySQLが遅いと感じたら、まずこの順番で確認してみてください。
-
EXPLAIN でフルスキャン(
type: ALL)を検出 - SHOW INDEX でインデックスの有無を確認し、必要なら追加
- スロークエリログで遅いクエリを洗い出す
- SELECT * を必要なカラムだけに絞る
- N+1クエリをJOINやサブクエリでまとめる
多くのケースはインデックスを適切に追加するだけで劇的に改善します。まずは EXPLAIN を習慣にするところから始めると良いと思います。
DBの設計・最適化を相談したい方へ
「クエリが遅い」「テーブル設計を見直したい」「データ移行をお願いしたい」など、データベース周りの受託相談を受け付けています。