タイトル: MySQLで実行計画を表示する方法
SEOタイトル: MySQL 実行計画 (EXPLAIN) の完全ガイド(FORMAT=JSON / ANALYZE / Visual Explain)
| この記事の要点 |
|
EXPLAIN の基本
SQL の実行計画 (Execution Plan) は、オプティマイザが「どの索引を使い、どの順でテーブルにアクセスするか」を表したもの。性能チューニングには必須の情報です。
-- 基本
EXPLAIN SELECT * FROM users WHERE email = 'alice@example.com';
-- 出力例
+----+-------------+-------+------+-------------------+----------+---------+------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+-------------------+----------+---------+------+------+--------------------------+
| 1 | SIMPLE | users | ref | idx_users_email | idx_em.. | 768 | const| 1 | Using where; Using index |
+----+-------------+-------+------+-------------------+----------+---------+------+------+--------------------------+
主要フィールドの意味
| 列 | 意味 | 注目ポイント |
|---|---|---|
id | クエリ内の SELECT 通し番号 | サブクエリ / UNION で複数になる |
select_type | SIMPLE / PRIMARY / SUBQUERY / DERIVED / UNION ... | 派生テーブルが多いと遅い兆候 |
table | 対象テーブル名(alias) | JOIN 順を読む手がかり |
type | アクセス種別(★ 最重要) | ALL は黄信号、const/eq_ref が良 |
possible_keys | 利用候補のインデックス | 空ならインデックス無し |
key | 実際に使われたインデックス | NULL なら全件スキャン |
key_len | 使われたインデックスの長さ | 複合 index でどこまで効くか |
rows | 読むと推定される行数 | 大きいと再考の余地 |
filtered | WHERE 通過率の推定 % | 低いと WHERE で大量フィルタリング |
Extra | 追加情報(Using where 等) | 「Using filesort」「Using temporary」は要注意 |
type の階層(速い順)
| type | 説明 | 速度 |
|---|---|---|
| system | テーブル 1 行のみ | ★★★★★ |
| const | 主キー / UNIQUE 一致で 1 行 | ★★★★★ |
| eq_ref | JOIN 相手から主キー / UNIQUE で 1 行ずつ | ★★★★ |
| ref | 非ユニーク index で複数行 | ★★★ |
| range | index 範囲スキャン (BETWEEN 等) | ★★★ |
| index | index 全スキャン(テーブル全件より速い) | ★★ |
| ALL | テーブル全件スキャン(フルテーブルスキャン) | ★ |
Extra でよく出る警告
| Extra | 意味 | 対処 |
|---|---|---|
| Using where | WHERE で行をフィルタリング | 通常は問題なし |
| Using index | Covering Index(テーブル本体を読まずに済む) | ★ 良い兆候 |
| Using filesort | ORDER BY のために追加のソート | ORDER BY 列にも index、または複合 index 順序を見直し |
| Using temporary | 中間結果のために一時テーブル作成 | GROUP BY / DISTINCT 見直し |
| Using join buffer | JOIN に index 無し | JOIN 条件カラムに index 追加 |
| Range checked for each record | JOIN 中に range アクセスを毎行判定 | JOIN 順や index を見直し |
EXPLAIN FORMAT=JSON
各演算子のcost / 読み取り bytes / 行推定まで詳細表示します。チューニングには必須。
EXPLAIN FORMAT=JSON
SELECT u.id, u.name, o.total
FROM users u
JOIN orders o ON u.id = o.user_id
WHERE u.email = 'alice@example.com';
-- 出力(抜粋)
{
"query_block": {
"select_id": 1,
"cost_info": { "query_cost": "2.85" },
"nested_loop": [
{
"table": {
"table_name": "u",
"access_type": "ref",
"key": "idx_users_email",
"rows_examined_per_scan": 1,
"rows_produced_per_join": 1,
"filtered": "100.00",
"cost_info": { "read_cost": "0.25", "eval_cost": "0.10" }
}
},
{ "table": { "table_name": "o", "access_type": "ref", ... } }
]
}
}
EXPLAIN ANALYZE (MySQL 8.0.18+)
実際にクエリを実行して、推定値ではなく実測の時間と行数を返します。推定と実測の乖離を発見するのに有用。
EXPLAIN ANALYZE
SELECT * FROM orders WHERE user_id = 100 ORDER BY created_at DESC LIMIT 10;
-- 出力例
-> Limit: 10 row(s) (cost=4.20 rows=10) (actual time=0.124..0.142 rows=10 loops=1)
-> Sort: orders.created_at DESC, limit input to 10 rows per chunk
-> Index lookup on orders using idx_user_id (user_id=100)
(cost=4.20 rows=42) (actual time=0.050..0.110 rows=42 loops=1)
-- ★ cost=4.20 rows=42 が推定
-- ★ actual time=0.050..0.110 rows=42 が実測
-- 推定と実測が大きく違う場合 → 統計が古い (ANALYZE TABLE 必要)
Visual Explain (MySQL Workbench)
MySQL Workbench を使うと、実行計画をカラフルなツリー図で見られます。
- クエリを書く
- 稲妻アイコン横の下三角 → 「Visual Explain」を選択
- 各ノードの色: 緑(高速)→ 黄 → 赤(遅い)
- 赤いノードにマウスオーバー → 詳細とアドバイス表示
SHOW WARNINGS でリライト後のクエリを見る
EXPLAIN SELECT * FROM users WHERE id IN (SELECT user_id FROM orders);
SHOW WARNINGS;
-- → オプティマイザが書き換えた実際のクエリが見える
-- → サブクエリが JOIN に変換されていることが多い
パフォーマンススキーマで統計を取る
-- 全クエリの平均実行時間 TOP 10
SELECT digest_text, count_star, avg_timer_wait/1e9 AS avg_ms
FROM performance_schema.events_statements_summary_by_digest
ORDER BY avg_timer_wait DESC
LIMIT 10;
-- 特定テーブルへのアクセス統計
SELECT object_schema, object_name, count_star, sum_timer_wait/1e9 AS sum_ms
FROM performance_schema.table_io_waits_summary_by_table
WHERE object_schema = DATABASE()
ORDER BY sum_timer_wait DESC;
PostgreSQL の EXPLAIN との比較
-- PostgreSQL
EXPLAIN SELECT * FROM users WHERE email = 'alice@example.com';
EXPLAIN ANALYZE SELECT * FROM users WHERE email = 'alice@example.com';
EXPLAIN (ANALYZE, BUFFERS, FORMAT JSON) SELECT ...;
-- BUFFERS の例
-> Index Scan using idx_users_email on users (cost=0.42..8.44 rows=1 width=64)
(actual time=0.025..0.027 rows=1 loops=1)
Index Cond: (email = 'alice@example.com')
Buffers: shared hit=4 ← バッファヒット 4 ブロック
I/O Timings: read=0.000 write=0.000
-- pgAdmin / DataGrip でグラフィカルな実行計画も見られる
典型的な改善パターン
| 症状 | 原因 | 対処 |
|---|---|---|
type=ALL | WHERE 列に index 無し | CREATE INDEX |
Using filesort | ORDER BY が index と不一致 | 複合 index で並び順吸収 |
Using temporary | GROUP BY / DISTINCT が index 外 | GROUP BY 列にも index |
| rows 推定が実測と大きく違う | 統計古い | ANALYZE TABLE |
JOIN で type=ALL | JOIN 条件に index 無し | FK 列に index |
FAQ
Q: 本番の遅いクエリを後追いで EXPLAIN したい
A: スロークエリログ + mysqldumpslow または pt-query-digest で抽出 → EXPLAIN し直し。Performance Schema からも統計取得可。
Q: EXPLAIN と実際の実行で計画が変わる?
A: 統計情報が古い、サーバ負荷、バインド変数の値などで変わることがあります。本番環境で EXPLAIN ANALYZE を直接打つのが確実(読み取りクエリのみ)。
Q: UPDATE や DELETE も EXPLAIN できる?
A: MySQL 5.6+ で可能。EXPLAIN UPDATE / DELETE / INSERT で実行計画が出ます。実際には更新されません(EXPLAIN ANALYZE は実行するので注意)。