チューニング

MySQL の実行計画

MySQL EXPLAIN の読み方。type / key / rows / Extra と 8.0 の ANALYZE・FORMAT=TREE。

MySQL の実行計画 diagram

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 等)
sql
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意味典型例判定
1systemテーブルに 1 行しかない特殊ケースMEMORY エンジンの 1 行テーブルOK
2constPK / UNIQUE で定数 1 行に確定WHERE id = 100OK
3eq_refJOIN で相手を PK / UNIQUE で 1 行取得JOIN ... ON t.id = u.idOK
4ref非ユニークなインデックスで等値検索WHERE user_id = 42OK
5rangeインデックスで範囲検索WHERE created_at > ?IN (...)BETWEENOK(ここが目標ライン)
▲ ここから下は改善候補 ▲
6indexインデックス全体を走査(カバリングで回避されることもある)ソートだけインデックス順、WHERE は使われていない要注意
7ALLフルテーブルスキャンインデックス無し / 関数適用で使われない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 filesortUsing 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 と同じ)。

sql
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」や外部ツールで可視化するのが現実的です。

sql
EXPLAIN FORMAT=JSON
SELECT * FROM orders WHERE user_id = 42;

EXPLAIN FORMAT=TREE
SELECT * FROM orders WHERE user_id = 42;