Day16 前半のゴール
「EXPLAIN を“怖い表”じゃなくて“SQLの行動ログ”として読めるようになる」
ここからいよいよ「パフォーマンスと設計」の世界に入ります。
Day16 の主役は EXPLAIN、つまり「実行計画」です。
一言で言うと、実行計画はこうです。
その SQL を実行するときに、MySQL が「どうやってデータを取りに行くつもりか」を教えてくれるもの
Day16 前半のゴールは、
EXPLAIN を実行して結果の表を出せる
その表の“どこを最初に見ればいいか”が分かる
「これは速そう/これは危なそう」をざっくり判別できる
ここまで行けば、後半でインデックスとの関係を深掘りするときに、かなり楽になります。
実行計画とは何か
「SQL の“作戦会議の結果”をのぞき見る」
SQL を投げるとき、MySQL の中ではこんなことが起きています。
どのテーブルから先に読むか
どのインデックスを使うか
何行くらい読みに行くつもりか
これを決めるのが「オプティマイザ(最適化エンジン)」です。
オプティマイザが決めた「作戦」が、実行計画です。
EXPLAIN は、その作戦を人間が読める形で見せてくれるコマンドです。
EXPLAIN
SELECT *
FROM users
WHERE email = 'foo@example.com';
Pythonこう書くと、「この SELECT をどう実行するつもりか」が表形式で返ってきます。
実行計画は、「結果」ではなく「やり方」です。
ここを一度、はっきり分けておきましょう。
結果
→ 実際に返ってくる行データ
実行計画
→ その行データを取るために、MySQL がどう動くか
あなたがパフォーマンスを考えるときに見るべきなのは、後者です。
まずは超シンプルな例で EXPLAIN を叩いてみる
「1テーブル・WHERE 1条件の世界で“形”に慣れる」
次のようなテーブルを用意したとします。
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50) NOT NULL,
email VARCHAR(100) NOT NULL,
INDEX idx_email (email)
) ENGINE=InnoDB;
Pythonそして、こんなクエリを考えます。
SELECT *
FROM users
WHERE email = 'foo@example.com';
Pythonこれに対して EXPLAIN を実行します。
EXPLAIN
SELECT *
FROM users
WHERE email = 'foo@example.com';
Pythonすると、だいたいこんな感じの表が返ってきます(簡略化しています)。
| id | select_type | table | type | possible_keys | key | rows | Extra |
|---|---|---|---|---|---|---|---|
| 1 | SIMPLE | users | ref | idx_email | idx_email | 1 | NULL |
最初は「カラム多すぎて無理…」ってなると思います。
なので、前半では“最低限ここだけ見ればいい”というポイントに絞ります。
EXPLAIN で最初に見るべきカラム
「table / type / key / rows の4つに絞る」
いきなり全部を理解しようとしなくて大丈夫です。
まずは、この4つだけに絞りましょう。
table
どのテーブルについての行か
type
どんなアクセス方法で行を取りに行くか(ここが超重要)
key
実際にどのインデックスが使われたか
rows
何行くらい読むつもりか(見積もり)
さっきの結果を、この4つに絞って見直します。
| table | type | key | rows |
|---|---|---|---|
| users | ref | idx_email | 1 |
これだけなら、だいぶ読みやすくなったはずです。
table カラム
「どのテーブルが“何回”出てくるかを見る」
table カラムは、その行が「どのテーブルに対するアクセスか」を表します。
1テーブルだけのクエリなら、1行だけです。
JOIN が増えると、テーブルの数だけ行が増えます。
例えば、次のようなクエリを EXPLAIN すると、
SELECT *
FROM orders
JOIN users ON orders.user_id = users.id;
Python結果には orders と users の2行が出てきます。
前半の段階では、
「このクエリは、何テーブルにアクセスしているのか」
「どのテーブルから先に読んでいるのか(id の順番)」
くらいを意識しておけば十分です。
type カラム(超重要)
「“どれくらい雑に読んでいるか”の指標」
type は、実行計画の中でも特に重要なカラムです。
一言で言うと、
どれくらい効率よく行を取りに行っているか
のざっくりしたランクです。
全部を覚える必要はありませんが、
前半で押さえておきたいのはこの3つです。
ALL
テーブル全体を端から端まで読む(フルスキャン)。基本的に遅い。
ref / eq_ref
インデックスを使って、条件に合う行だけを取る。だいたい「良い」状態。
const
1行だけをピンポイントで取る。かなり「良い」状態。
さっきの例では、type = ref でした。
| table | type | key | rows |
|---|---|---|---|
| users | ref | idx_email | 1 |
これは、
インデックス idx_email を使って、条件に合う行だけを取っている
という意味で、「良い」状態です。
逆に、もしこうなっていたら要注意です。
| table | type | key | rows |
|---|---|---|---|
| users | ALL | NULL | 10000 |
type = ALL は「テーブル全体をなめている」状態です。key = NULL は「インデックスを使っていない」ことを意味します。
rows が 10000 なら、1万行全部を読んでから WHERE で絞り込んでいる、ということです。
テーブルが大きくなると、ここがボトルネックになります。
Day16 前半では、
type が ALL なら「ちょっと危ないかも」
type が ref / const なら「とりあえず良さそう」
くらいの感覚を持てればOKです。
key カラム
「“どのインデックスを実際に使ったか”を見る」
key カラムは、「実際に使われたインデックスの名前」です。
さっきの例では、
| table | type | key | rows |
|---|---|---|---|
| users | ref | idx_email | 1 |
となっていて、idx_email が使われています。
もし、テーブルにインデックスを作っているのに、key = NULL になっていたら、
インデックスはあるけど、このクエリでは使われていない
ということになります。
インデックスを作ったのに遅いまま、というときは、
まずここを疑います。
「インデックスがない」のか
「インデックスはあるけど、使われていない」のか
この2つは、対策が全然違うので、
EXPLAIN で切り分けられるようになると一気に楽になります。
rows カラム
「“どれくらい読みに行くつもりか”のざっくり見積もり」
rows は、「このテーブルから何行くらい読むつもりか」の見積もりです。
実際の行数ではなく、統計情報からの予測ですが、感覚をつかむには十分です。
例えば、
| table | type | key | rows |
|---|---|---|---|
| users | ref | idx_email | 1 |
なら、「1行くらい読めば済みそう」と判断している、ということです。
一方、
| table | type | key | rows |
|---|---|---|---|
| users | ALL | NULL | 100000 |
なら、「10万行くらい読むことになりそう」と見積もっているわけです。
JOIN が絡むと、複数テーブルの rows が掛け算的に効いてくるので、
rows が大きいテーブルがどれか、というのは意識しておくとよいです。
Day16 前半では、
rows がやたら大きいのに type が ALL だと「かなり危ない」
rows が小さくて type も ref / const なら「だいぶ安心」
くらいのざっくり感覚で十分です。
まとめて読む練習:良いパターンと悪いパターン
「“EXPLAIN を見て一言コメントできる”ところまで行く」
ここまでの話を、2つのパターンでまとめてみます。
良さそうなパターン(インデックスが効いている)
| table | type | key | rows |
|---|---|---|---|
| users | ref | idx_email | 1 |
コメント例:
users テーブルに対して、idx_email インデックスを使って、条件に合う1行だけを取っている。type も ref で rows も1なので、かなり効率よく取れていそう。
危なそうなパターン(フルスキャン)
| table | type | key | rows |
|---|---|---|---|
| users | ALL | NULL | 100000 |
コメント例:
users テーブルをインデックスなしで全件スキャンしていて、10万行くらい読むつもりになっている。WHERE 条件に合う行が少ないなら、インデックスを貼るか、クエリを書き直す余地がありそう。
Day16 前半の目標は、「このくらいのコメントを自分の言葉で言える」ことです。
全部のカラムを完璧に理解する必要はありません。
Day16 前半のまとめ
実行計画は「SQL の結果」ではなく「その結果を取るために MySQL がどう動くつもりか」という“作戦書”であり、EXPLAIN SELECT ... と書くことで、その作戦を表形式で確認できる。
最初からすべてのカラムを理解する必要はなく、まずは table(どのテーブルか)、type(アクセス方法のざっくりランク)、key(実際に使われたインデックス)、rows(読みに行くつもりの行数の見積もり)の4つに絞って、「type が ALL で key が NULL かつ rows が大きいときは“フルスキャンで危ない”、type が ref / const で key にインデックス名が入り rows が小さいときは“インデックスが効いていて良さそう”」という感覚を持てれば、実務で「このクエリ、なんか遅いな」と感じたときに、EXPLAIN を叩いて一言コメントできるレベルには立てる。
後半では、
JOIN が絡んだときの EXPLAIN の読み方、possible_keys と key の違い、Extra カラムに出てくる「Using index」「Using filesort」などの意味を、
インデックス設計と結びつけながらもう一段深く見ていきます。

