こんにちは、かつコーチです。
前回はインデックスの種類を整理しましたが、「インデックスを張ったのに速くならない」という相談は実務でも頻繁に起こります。
原因を切り分けるには、MySQLが実際にどうクエリを実行しているかを可視化するEXPLAINが欠かせません。
今回は上級者向けの内容として、EXPLAINの各カラムの読み方と、遅いクエリを見抜くポイントを解説します。
EXPLAINの基本的な使い方
クエリの先頭にEXPLAINを付けるだけ
EXPLAINは、対象のSELECT文の先頭に付けるだけで実行計画(オプティマイザがどう実行しようとしているか)を出力してくれます。
EXPLAIN
SELECT o.id, o.status, c.name
FROM orders o
JOIN customers c ON o.customer_id = c.id
WHERE o.status = 'paid'
ORDER BY o.created_at DESC
LIMIT 20;
MySQL 8.0ではEXPLAIN FORMAT=TREEやEXPLAIN ANALYZEも使え、EXPLAIN ANALYZEは実際にクエリを実行した上で、各ステップの実測コストと実行時間まで出してくれます。
EXPLAIN ANALYZE
SELECT o.id, o.status, c.name
FROM orders o
JOIN customers c ON o.customer_id = c.id
WHERE o.status = 'paid'
ORDER BY o.created_at DESC
LIMIT 20;
見積もりだけのEXPLAINと、実測を伴うEXPLAIN ANALYZEを使い分けることで、オプティマイザの想定と現実のズレも把握できます。
各カラムの読み方
type:アクセス方法を示す最重要カラム
typeは、そのテーブルへのアクセス方法を示す最も重要なカラムで、良い順に並べると次のようになります。
| type | 意味 |
|---|---|
const / system | 主キー・ユニークキーで1行に確定 |
eq_ref | JOINで相手側が主キー・ユニークキー1行に確定 |
ref | 非ユニークインデックスでの等価検索 |
range | インデックスを使った範囲検索 |
index | インデックスのフルスキャン |
ALL | フルテーブルスキャン |
type: ALLが出ている場合はインデックスがまったく使われておらず、テーブルの全行を読んでいる状態なので、まず疑うべきポイントです。
key・possible_keys:使われたインデックス
possible_keysはオプティマイザが候補として検討したインデックス、keyは実際に採用されたインデックスを示します。
possible_keysに候補があるのにkeyがNULLになっている場合、インデックスがあっても使われなかったことを意味し、原因調査が必要なサインです。
rows・filtered:見積もり行数と絞り込み率
rowsはそのステップで読み込むと見積もられた行数、filteredはWHERE条件でさらに絞り込まれる割合(%)です。
rowsが実際のテーブル行数に近い、あるいはfilteredの値が極端に低い場合は、インデックスの効きが悪く、多くの行を無駄に読んでいる可能性が高いです。
Extra:見落としがちな要注意項目
Extraカラムには補足情報が入り、次のような表示には注意が必要です。
-- ソート対象がインデックス順で取れず、追加のソート処理が発生している例
EXPLAIN
SELECT * FROM orders WHERE customer_id = 1001 ORDER BY total_amount DESC;
Using filesort:インデックス順で取得できず、別途ソート処理が発生しているUsing temporary:一時テーブルを作って処理している(GROUP BYやDISTINCTで発生しやすい)Using index:インデックスだけでデータが取れている(カバリングインデックス、テーブル本体へのアクセスが不要で高速)
Using filesortとUsing temporaryは特にパフォーマンスへの影響が大きく、この2つが出ていないかは毎回チェックする習慣をつけておくべきです。
実行計画から改善につなげる
インデックスが使われないケースの典型例
keyがNULLになりやすい典型例をいくつか挙げます。
-- ❌ カラムに関数をかけると、そのカラムのインデックスは使われない
EXPLAIN SELECT * FROM orders WHERE YEAR(created_at) = 2026;
-- ✅ 範囲検索に書き換えることでインデックスが使われる
EXPLAIN SELECT * FROM orders WHERE created_at BETWEEN '2026-01-01' AND '2026-12-31 23:59:59';
-- ❌ カラムの型と異なる型で比較すると暗黙の型変換が起き、インデックスが使われないことがある
EXPLAIN SELECT * FROM orders WHERE order_code = 12345; -- order_codeはVARCHAR
-- ✅ カラムの型に合わせて比較する
EXPLAIN SELECT * FROM orders WHERE order_code = '12345';
インデックス対象のカラムに関数や演算、暗黙の型変換をかけてしまうと、インデックスの木構造をそのまま辿れなくなるため、オプティマイザがフルスキャンを選んでしまいます。
私が実行計画を読み違えた失敗談
以前、ORDER BYにLIMITを組み合わせたクエリが遅いという相談を受け、EXPLAINのtypeがrefになっていたので「インデックスは使えているから原因は別だろう」と判断してしまったことがあります。
実際はExtraにUsing filesortが出ており、WHERE句の絞り込みにはrefでインデックスが効いていたものの、ORDER BYのソート自体は別処理で行われていたのが遅さの正体でした。
typeだけを見て「インデックスが効いているから問題ない」と判断せず、Extraまで含めて総合的に読む必要があると学んだ失敗でした。
最終的にはORDER BYのカラムを含む複合インデックスに張り替えることでUsing filesortが消え、レスポンスが体感できるレベルで改善しました。
まとめ
この記事のポイント
EXPLAINはSELECT文の先頭に付けるだけで実行計画を確認でき、EXPLAIN ANALYZEは実測値まで見られるtypeはアクセス方法を示す最重要カラムで、ALLはフルテーブルスキャンのサインpossible_keysにあるのにkeyがNULLはインデックス不使用のサインExtraのUsing filesortとUsing temporaryは特に注意して確認するtypeだけでなくExtraまで含めて総合的に読まないと、原因を見誤ることがある
次に読むべき記事
次回は「スロークエリログの見方と改善の第一歩」です。
EXPLAINで個別クエリを分析する前段階として、そもそも「どのクエリが遅いのか」を見つける方法を解説します。
タグ: MySQL, 上級者向け, パフォーマンス
