MySQL の実行計画
MySQL EXPLAIN の読み方。type / key / rows / Extra と 8.0 の ANALYZE・FORMAT=TREE。
MySQL の EXPLAIN は表形式
MySQL の EXPLAIN は、PostgreSQL のような木構造ではなく行ごとの表として出力されます。1 行 = 1 つのテーブル読み取りアクセス、列が各属性(どう読むか、どのインデックスを使うか、何行読む見込みか等)を表します。
- id: 実行 ID。同じ id は同じ SELECT ブロック、大きい id から先に実行
- select_type: SIMPLE / PRIMARY / SUBQUERY / DERIVED 等
- table: 対象テーブル
- type: アクセス方式(最重要。下で詳説)
- key: 実際に使われたインデックス
- rows: 読む見込みの行数(推定)
- Extra: 補足情報(Using where / Using index / Using filesort 等)
EXPLAIN
SELECT u.name, o.total
FROM users u JOIN orders o ON o.user_id = u.id
WHERE u.plan = 'pro';
-- 出力 (一部)
-- +----+----------+------+------+---------+------+
-- | id | table | type | key | rows | Extra|
-- +----+----------+------+------+---------+------+
-- | 1 | u | ref | plan | 200 | ... |
-- | 1 | o | ref | fk_u | 12 | ... |
-- +----+----------+------+------+---------+------+type 列は「良い ← 悪い」の序列がある
type はアクセス効率を表す最重要の列です。速い順に並べると次の表のようになります。目標ラインは range 以上。index / ALL が出ていたら改善対象です。
| 順位 | type | 意味 | 典型例 | 判定 |
|---|---|---|---|---|
| 1 | system | テーブルに 1 行しかない特殊ケース | MEMORY エンジンの 1 行テーブル | OK |
| 2 | const | PK / UNIQUE で定数 1 行に確定 | WHERE id = 100 | OK |
| 3 | eq_ref | JOIN で相手を PK / UNIQUE で 1 行取得 | JOIN ... ON t.id = u.id | OK |
| 4 | ref | 非ユニークなインデックスで等値検索 | WHERE user_id = 42 | OK |
| 5 | range | インデックスで範囲検索 | WHERE created_at > ?、IN (...)、BETWEEN | OK(ここが目標ライン) |
| ▲ ここから下は改善候補 ▲ | ||||
| 6 | index | インデックス全体を走査(カバリングで回避されることもある) | ソートだけインデックス順、WHERE は使われていない | 要注意 |
| 7 | ALL | フルテーブルスキャン | インデックス無し / 関数適用で使われない | NG(小テーブル以外) |
チューニングの第一歩は「ALL / index になっている行を range 以上に引き上げる」こと。該当列にインデックスがないか、あるいは WHERE DATE(created_at) = ? のようにインデックスを使わせない書き方をしていないかを確認します。
Extra 列で重要なシグナル
Extra には実行エンジンのヒントが出ます:
- Using where: WHERE で追加フィルタ。インデックスでは絞り切れず、テーブル取得後に条件評価
- Using index: カバリング成功。インデックスだけで完結(速い)
- Using index condition: ICP(Index Condition Pushdown)。インデックス段階で条件評価
- Using filesort: 明示的ソートが必要。インデックス順に取れない ORDER BY。警戒シグナル
- Using temporary: 一時テーブル生成。GROUP BY や DISTINCT で発生しやすく、メモリ→ディスクに溢れると激遅
- Using join buffer (Block Nested Loop): 結合キーにインデックスがない。インデックス追加を検討
Using filesort と Using temporary の組み合わせは典型的な遅延原因。ORDER BY や GROUP BY の対象列にインデックスを足せば消せることが多いです。
MySQL 8.0 の EXPLAIN ANALYZE
MySQL 8.0.18 以降は EXPLAIN ANALYZE が使えます。PostgreSQL と似た木構造 + 実測値の出力で、各演算子のactual time / actual rows / loops が表示されます。従来の表形式 EXPLAIN より情報量が多く、JOIN 順序や遅延箇所が一目で分かります。
古い MySQL(5.7 以前)では使えない点、そして EXPLAIN ANALYZE も実際にクエリを実行する点に注意(PostgreSQL と同じ)。
EXPLAIN ANALYZE
SELECT u.name, COUNT(o.id)
FROM users u LEFT JOIN orders o ON o.user_id = u.id
GROUP BY u.name;
-- 出力 (例):
-- -> Group aggregate: count(o.id)
-- (actual time=0.2..3.5 rows=120 loops=1)
-- -> Nested loop left join
-- (actual time=0.05..2.8 rows=1500 loops=1)FORMAT=JSON / TREE で詳細を取る
表形式では足りないときはFORMAT オプションが便利です:
EXPLAIN FORMAT=JSON ...: コスト推定値、使用候補インデックス、各ノードの詳細を JSON で出力。プログラムで解析もしやすいEXPLAIN FORMAT=TREE ...: MySQL 8.0 の木構造表示(EXPLAIN ANALYZE と同じ形)
JSON 出力は情報量が多い代わりに読みにくいので、MySQL Workbench の「Visual Explain」や外部ツールで可視化するのが現実的です。
EXPLAIN FORMAT=JSON
SELECT * FROM orders WHERE user_id = 42;
EXPLAIN FORMAT=TREE
SELECT * FROM orders WHERE user_id = 42;