type — アクセス方法(上ほど効率的)
| type |
意味 |
例 |
const |
PKまたはユニークインデックスで1行だけ取得。最速 |
WHERE id = 1 |
eq_ref |
JOINで内部表に毎回1行だけマッチ。constの次に速い |
JOIN時にPKで結合 |
ref |
非ユニークインデックスで複数行にマッチ |
WHERE country_code = 'JPN' |
range |
インデックスを使った範囲スキャン |
WHERE id BETWEEN 10 AND 20 |
index |
インデックス全体のスキャン(行本体へのアクセスなし) |
カバリングインデックス時によく出る |
ALL |
フルテーブルスキャン。インデックスなしか使われていない |
要改善のサイン |
Extra — 補足情報
| Extra |
意味 |
危険度 |
Using index |
カバリングインデックス。行本体へのアクセスなし。効率的 |
✅ 良い |
Using where |
行フェッチ後にWHERE条件でフィルタ。普通 |
普通 |
Using index condition |
インデックスアクセス+行本体フェッチが発生 |
普通 |
Using temporary |
内部テンポラリテーブルが発生。GROUP BY / Window関数 |
⚠️ 注意 |
Using filesort |
追加ソートが発生。ORDER BYのインデックスなし |
⚠️ 注意 |
Using temporary + Using filesort
|
両方発生。重い処理のサイン。インデックス追加を検討 |
🔴 要改善 |
NULL |
特記事項なし |
普通 |
主要カラムの意味
| カラム |
意味 |
rows |
フェッチする見積もり行数。少ないほど良い |
filtered |
rowsのうちWHERE条件で残る割合(%)。インデックスやヒストグラムがないカラムでは固定値(= なら10.00) |
key |
実際に選ばれたインデックス。NULLならインデックス未使用 |
possible_keys |
使える候補インデックス。keyと違う場合は意図と異なるインデックスが選ばれている可能性あり |
select_type の値
| select_type |
意味 |
SIMPLE |
サブクエリなしの単純なSELECT |
PRIMARY |
一番外側のクエリ |
DERIVED |
FROM句サブクエリ(派生テーブル) |
DEPENDENT SUBQUERY |
相関サブクエリ(N+1のサイン) |
EXPLAIN ANALYZE — 実測値の読み方
-> Filter: (country.Continent = 'Asia')
(cost=25.7 rows=34.1) (actual time=0.176..0.592 rows=51 loops=1)
↑見積もりコスト ↑見積もり行数 ↑最初の行まで ↑全行まで ↑実測行数
| 項目 |
意味 |
cost |
オプティマイザの見積もりコスト |
rows(costの後) |
見積もり行数 |
actual time=A..B |
最初の行を返すまでAms、全行返すまでBms |
rows(actual timeの後) |
実測行数 |
loops |
そのステップが何回実行されたか |
見積もりと実測のズレが大きい箇所 → ヒストグラム追加(ANALYZE TABLE t UPDATE HISTOGRAM ON col)を検討
JOINの実行計画(NLJ)
| 状況 |
読み方 |
id が同じ |
上が駆動表、下が内部表。駆動表の行数 = ループ回数 |
id が違う |
id が大きい方が先に処理される |
select_type: DERIVED |
FROM句サブクエリ。id が大きい方が先に処理された派生テーブル |
遅いクエリを見たときのチェックリスト
| チェック |
症状 |
対処 |
| ① |
type: ALL |
インデックスがない or 使われていない → インデックス追加を検討 |
| ② |
Extra: Using temporary |
GROUP BYのカラムにインデックスなし → インデックス追加またはクエリ見直し |
| ③ |
filtered が低い(10.00など固定値) |
オプティマイザの見積もりが不正確 → ヒストグラム追加 |
| ④ |
select_type: DEPENDENT SUBQUERY |
相関サブクエリ(N+1)→ JOINまたはWITH句に書き直し |
| ⑤ |
rows が多い |
フェッチ行数が多い → 複合インデックスやカバリングインデックスを検討 |
よく使うコマンド
-- 実行計画を確認
EXPLAIN SELECT ...;
-- 実測値も確認(実際に実行される)
EXPLAIN ANALYZE SELECT ...;
-- ヒストグラムを追加してfilteredの精度を改善
ANALYZE TABLE city UPDATE HISTOGRAM ON Name;
-- ヒストグラムを削除
ANALYZE TABLE city DROP HISTOGRAM ON Name;
-- 実際に何行読んだか確認
FLUSH STATUS;
-- クエリを実行
SHOW SESSION STATUS LIKE 'Handler_read%';
-- ロック状況を確認
SELECT object_name, index_name, lock_type, lock_mode, COUNT(*) AS locked_rows
FROM performance_schema.data_locks
WHERE lock_type = 'RECORD'
GROUP BY object_name, index_name, lock_type, lock_mode;
-- デッドロックの詳細を確認
SHOW ENGINE INNODB STATUS\G
lock_mode の読み方
| lock_mode |
意味 |
X |
ネクストキーロック(レコード+手前のギャップ)。REPEATABLE-READで発生 |
X,REC_NOT_GAP |
ギャップなし排他ロック(その行だけ)。READ-COMMITTEDや=検索で発生 |
X,GAP |
ギャップのみのロック |
S |
共有ロック(FOR SHARE) |