MySQL クエリ実行計画の構造と最適化指針

MySQL のクエリ実行計画(Execution Plan)は、SQL 文が実際に物理的に実行される際の処理手順を事前に可視化する機能です。オプティマイザは複数の実行戦略を評価し、I/O 量・CPU 使用率・メモリ消費などのコストに基づいて最適なパスを選択します。EXPLAIN 文を用いることで、その選択結果を各フィールドで詳細に確認できます。

基本的な使用例

EXPLAIN SELECT u.* 
FROM users u
WHERE u.id IN (
  SELECT ua.user_id 
  FROM user_locations ua 
  WHERE ua.city = '長沙市 麓谷'
);

id:クエリ構成単位の識別子

SELECT 文のネスト深度や実行順序を示す整数値です。

  • 同一値 → 上から下へ順次実行
  • 異なる値 → 数値が大きいほど優先度が高く、先に実行される(例:サブクエリの内側が先)

select_type:クエリの論理的分類

意味 適用例
SIMPLE単一テーブル・非サブクエリSELECT * FROM products;
PRIMARY複合クエリの最外層UNION やサブクエリ全体のラッパー
MATERIALIZEDIN 句内のサブクエリが一時テーブル化された場合WHERE id IN (SELECT ...)
UNIONUNION の第2以降の構成要素SELECT ... UNION SELECT ...
UNION RESULTUNION 結果のマージ段階結合後の最終出力処理

table:参照対象のテーブル名

以下の形式で表示されます:

  • 通常:テーブル名またはエイリアス(u, ua
  • 一時構造:<union2,3>(ID 2 と 3 の結果を結合)
  • サブクエリ:<subquery4>(ID 4 のサブクエリ結果)

type:アクセス方法(パフォーマンス指標の核心)

効率順に並べると:NULL > system > const > eq_ref > ref > range > index > ALL。この値は、インデックス活用の質を直接反映します。

system / const:定数レベルの高速アクセス

-- system:システムテーブル(メモリ上常駐)
EXPLAIN SELECT * FROM performance_schema.global_status LIMIT 1;

-- const:主キー/ユニークインデックス+定数条件
EXPLAIN SELECT * FROM accounts WHERE account_id = 98765;

eq_ref / ref:JOIN 時のインデックス利用

-- eq_ref:PK/NOT NULL UNIQUE による1:1マッチ
EXPLAIN SELECT p.name, o.amount 
FROM products p 
JOIN orders o ON p.product_id = o.product_id;

-- ref:非ユニークインデックス(複数行ヒット可能性あり)
ALTER TABLE orders DROP INDEX idx_product_id;
CREATE INDEX idx_product_id ON orders(product_id);
EXPLAIN SELECT * FROM orders WHERE product_id = 101;

range / index / ALL:範囲・全インデックス・全表走査

-- range:インデックス上の範囲検索
EXPLAIN SELECT * FROM logs WHERE created_at BETWEEN '2024-01-01' AND '2024-06-30';

-- index:インデックス全走査(テーブル行へのアクセスなし)
EXPLAIN SELECT COUNT(*) FROM accounts;

-- ALL:フルテーブルスキャン(避けるべき)
EXPLAIN SELECT * FROM customers WHERE postal_code LIKE '%123%';

possible_keys と key:インデックス選択の裏側

  • possible_keys:統計情報に基づき「使えるかもしれない」インデックス候補
  • key:オプティマイザが実際に採用したインデックス名(NULL=未使用)

両者の不一致は、カバリングインデックス利用や統計情報の陳腐化を示唆します。

key_len:インデックスの有効利用長

使用されたインデックス列のバイト数。文字列型では文字コードと長さから算出され、utf8mb4 の VARCHAR(50) なら最大 200 バイト(50 × 4)になります。部分一致(LIKE 'abc%')では、前方一致部分のみがカウントされます。

rows:推定読み取り行数

オプティマイザが予測した、該当条件を満たすために走査する行数の平均値です。実際の結果件数とは異なり、JOIN の中間ステップでの累積読み取り量を示します。大幅な過小・過大評価は、ANALYZE TABLE 実行で統計情報を更新することで改善可能です。

filtered:フィルタ効率率

読み取った行のうち、WHERE 条件を通過した割合(%)。値が 100 に近いほど、インデックス選択と条件絞り込みが効率的であることを意味します。

Extra:補足情報(性能ボトルネックのサイン)

意味 対応策
Using filesortソートに外部ファイルを使用(インデックス未活用)ORDER BY 対象列にインデックス作成
Using temporaryGROUP BY / DISTINCT で一時テーブル生成集約対象と並び順を同一インデックスでカバー
Using indexカバリングインデックスで行データアクセス不要SELECT 列をインデックス列に含める設計
Using index conditionICP(Index Condition Pushdown)有効複合インデックスの左方一致を活かす
Using whereWHERE 句による行フィルタリング実施中正常動作(ただし、index 未使用時は注意)
Select tables optimized awayMIN/MAX/COUNT(*) がインデックス構造で即時解決主キー・ユニーク制約の活用

具体例:filesort と temporary の検出

-- filesort 発生(sex にインデックスなし)
EXPLAIN SELECT * FROM customers ORDER BY sex;

-- temporary 発生(group by と order by で別カラム)
EXPLAIN SELECT name, COUNT(*) 
FROM customers 
GROUP BY name 
ORDER BY region;

タグ: MySQL execution-plan query-optimization Indexing EXPLAIN

6月10日 20:42 投稿