MySQL | SQLite経験者向け、30日で習得するMySQL:実務SQL力 - Day8 JOINの最適化

SQL MySQL
スポンサーリンク

Day8 前半のゴール

「JOIN は“上から順に実行されない”ことを理解する」

ここからいよいよ「実務SQL力」に入っていきます。
Day8 のテーマは JOIN の最適化、その中でも「実行順序」です。

多くの人が最初にハマるポイントはこれです。

SQL は上から順に書いてあるように見えるけれど、
実際にその順番で実行されているとは限らない

特に JOIN が増えてくると、

どのテーブルから先に読まれるのか
どの条件が先に効くのか
インデックスがどこで使われるのか

をイメージできるかどうかで、
「なんとなく書いたSQL」と「意図を持って書いたSQL」の差が出ます。

Day8 前半のゴールは、

JOIN を含むSELECT文の“論理的な実行順序”を理解する
MySQLが「どのテーブルから先に読むか」を変えられることを知る
WHERE と ON の違いを、実行順序の観点から説明できる

ここまで行ければ、後半の EXPLAIN や最適化の話が一気に入ってきます。


まずはシンプルなJOINから

「“見た目の順番”と“実際の処理”を分けて考える」

例として、よくある users と orders のJOINを使います。

SELECT
  users.id,
  users.name,
  orders.id   AS order_id,
  orders.total
FROM users
JOIN orders
  ON users.id = orders.user_id
WHERE users.id = 1;
SQL

見た目だけ見ると、こう思いがちです。

  1. users を全部読む
  2. orders を全部読む
  3. ON でくっつける
  4. WHERE で users.id = 1 に絞る

でも、実際の処理はそんな素直ではありません。
MySQL は「できるだけ速く終わる順番」を自分で選びます。

例えば、インデックスがこうなっているとします。

users.id … PRIMARY KEY(インデックスあり)
orders.user_id … インデックスあり

この場合、MySQL はこんな動きを“選べる”立場にあります。

先に users から id=1 の行だけ取る(インデックスで一発)
→ その1行の id を使って、orders.user_id=1 を探す

つまり、「全部読む」のではなく、
「必要なところだけを順番に読む」ように最適化してくれます。

ここで大事なのは、

SQL の書き順と、実際の読み順は一致しない
MySQL はインデックスや統計情報を見て、順番を決めている

という事実です。


SQL の“論理的な実行順序”を一度整理する

「書く順番と“概念上の順番”を分けて覚える」

まず、「論理的にはこういう順番で処理される」という話をします。
JOIN を含むSELECT文は、概念的には次の順番で処理されます。

FROM
JOIN
ON
WHERE
GROUP BY
HAVING
SELECT
ORDER BY
LIMIT

実際の内部実装はもっと複雑ですが、
「頭の中でイメージする順番」としてはこれで十分です。

さっきのクエリに当てはめると、

FROM users
JOIN orders ON users.id = orders.user_id
→ ここで“結合された行の候補”ができる

WHERE users.id = 1
→ その候補から、users.id=1 の行だけ残す

SELECT で必要なカラムだけ取り出す

という流れになります。

ここでのポイントは、

ON は「結合するときの条件」
WHERE は「結合した後に残す行を決める条件」

という役割の違いです。


WHERE と ON の違いを、実行順序から見る

「“どこで絞るか”が意味を変える」

INNER JOIN の場合、
ON に書いても WHERE に書いても結果が同じになるケースが多いので、
違いを意識しないまま来ている人も多いです。

例えば、次の2つは結果が同じです。

SELECT *
FROM users
JOIN orders
  ON users.id = orders.user_id
WHERE users.id = 1;
SQL
SELECT *
FROM users
JOIN orders
  ON users.id = orders.user_id
 AND users.id = 1;
SQL

論理的には、

FROM users
JOIN orders ON users.id = orders.user_id AND users.id = 1

と書いた場合でも、
MySQL は「users.id=1 だけを先に取ってからJOINする」ように最適化できます。

ただし、LEFT JOIN になると話が変わります。
ここはDay8後半〜Day9あたりで深掘りするので、
今は「INNER JOINではONとWHEREの違いは結果に出にくいが、LEFT JOINでは出る」とだけ覚えておいてください。

Day8 前半で押さえたいのは、

ON は「結合の条件」
WHERE は「結合後のフィルタ」

という役割の違いです。


MySQL は“JOINの順番”を勝手に変えてよい

「FROMに書いた順番=実行順序ではない」

もう1つ重要な事実があります。

FROM users
JOIN orders

と書いたからといって、
必ず users → orders の順で読まれるとは限りません。

MySQL は、統計情報やインデックスを見て、

先に orders から絞った方が速い
先に users から絞った方が速い

と判断すれば、内部的なJOIN順序を入れ替えることができます。

例えば、こんなクエリを考えます。

SELECT *
FROM users
JOIN orders
  ON users.id = orders.user_id
WHERE orders.total >= 100000;
SQL

もし orders.total にインデックスがあって、
「100,000以上の注文は全体の1%しかない」ような状況なら、

先に orders から total>=100000 の行だけ取る
→ その user_id を使って users をJOINする

という順番の方が速いです。

SQLの見た目は「users → orders」ですが、
MySQLは「orders → users」で処理しても構いません。

ここでのポイントは、

JOINの最適化を考えるとき、
「書いた順番」ではなく「どの条件でどれだけ絞れるか」を見る

という視点が必要になる、ということです。


SQLite と MySQL のJOIN最適化の“温度差”

「SQLiteでは“なんとなく動く”、MySQLでは“本気でチューニング対象”」

SQLite でもJOINはできますが、
想定されるデータ量や用途の違いから、

JOINの最適化をそこまで意識しなくても済む
EXPLAINを見て細かくチューニングする機会が少ない

という世界観でした。

MySQL は、

大量データ
複数ユーザー同時アクセス
複雑なJOIN

が前提になることが多く、
JOINの実行順序やインデックスの使われ方が、
そのままレスポンス速度に直結します。

Day8 では、

SQLは“書いた順番どおりに実行されるわけではない”
MySQLは“速くするためにJOIN順序を変えてもよい”

という前提を、まず頭にしっかり入れておきます。


JOIN最適化のための“思考の入口”

「このクエリは、どのテーブルから先に絞るのが気持ちいいか?」

ここまでを踏まえて、
JOINを最適化するときの思考の入口を1つだけ持っておきましょう。

クエリを書いたら、心の中でこう自問します。

この条件なら、どのテーブルから先に絞るのが一番効率よさそう?

例えば、

WHERE users.id = ?
→ users から先に1行だけ取るのが良さそう

WHERE orders.total >= 100000
→ orders から「高額注文だけ」を先に取るのが良さそう

この「人間としての直感」と、
MySQLが実際に選んでいる実行計画(後半でEXPLAINを見る)を
照らし合わせていくのが、JOIN最適化のトレーニングになります。

Day8 前半では、
まだEXPLAINは出さなくていいので、

SQLは見た目どおりには動いていない
ONとWHEREには役割の違いがある
MySQLはJOIN順序を入れ替えられる

この3つを、しっかり言葉にできるようになっておいてください。


Day8 前半のまとめ

JOIN を含むSELECT文は、見た目の書き順とは別に、「FROM → JOIN → ON → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT」という“論理的な実行順序”で考えると整理しやすく、ON は「結合の条件」、WHERE は「結合後に残す行を決める条件」という役割の違いを持つ。
MySQL は、FROM に書いたテーブルの順番どおりに必ず実行するわけではなく、インデックスや統計情報を見て「どのテーブルから先に絞るか」「どの条件を先に使うか」を自動的に決めるため、FROM users JOIN orders ... WHERE orders.total >= 100000 のようなクエリでも、内部的には orders → users の順で処理されることがある。
INNER JOIN では、ON に書くか WHERE に書くかで結果が変わらないケースが多いが、論理的には「ONで結合候補を作り、その後WHEREでフィルタする」という流れになっており、この違いはLEFT JOINなどで結果に影響するため、「ON=結合条件/WHERE=結合後フィルタ」という意識を早めに持っておくことが重要になる。
SQLite ではJOIN最適化をあまり意識しなくても済む場面が多かったのに対し、MySQL では大量データ・複数ユーザー前提で、JOINの実行順序とインデックスの使われ方がレスポンスに直結するため、「このクエリはどのテーブルから先に絞るのが効率的か?」と考える癖を付けることが、実務SQL力への第一歩になる。

後半では、
実際にEXPLAINを使って「MySQLがどの順番でJOINしているか」を可視化し、
インデックスの有無や条件の書き方で実行計画がどう変わるかを、
具体的な例で追いかけていきます。

タイトルとURLをコピーしました