MySQL | SQLite経験者向け、30日で習得するMySQL:パフォーマンスと設計 - Day19 クエリチューニング②

SQL MySQL
スポンサーリンク

Day19 後半のゴール

「JOIN を“速くするための具体的な技術”を、自分で選んで使えるようになる」

前半では、JOIN が遅くなる理由と、EXPLAIN を使った原因特定の方法を整理しました。
後半では、いよいよ「どう改善するか」を具体的に掘り下げます。

ここでのゴールは次の状態です。

JOIN を速くするためのインデックス設計を説明できる
JOIN の順番がパフォーマンスにどう影響するか理解できる
JOIN を書き換えて速くする“実務的テクニック”を使える

JOIN は SQL の中でも最もパフォーマンス差が出る部分なので、
ここを押さえると実務レベルのチューニング力が一気に上がります。


JOIN を速くするためのインデックス設計

「JOIN の ON 句に出てくるカラムは“最優先で貼る”」

JOIN の改善で最も効果が大きいのは、やはりインデックスです。
ただし、貼る場所を間違えると効果が出ません。

JOIN の ON 句に出てくるカラムは、最優先でインデックス候補になります。

例として、前半で扱ったクエリを再掲します。

SELECT
  o.id,
  o.created_at,
  p.name,
  oi.quantity
FROM orders o
JOIN order_items oi ON o.id = oi.order_id
JOIN products p     ON oi.product_id = p.id
JOIN users u        ON o.user_id = u.id
WHERE u.id = 123
ORDER BY o.created_at DESC;
Python

この JOIN を速くするために必要なインデックスは、次のように整理できます。

orders.user_id
order_items.order_id
order_items.product_id
products.id(主キーなのでOK)
users.id(主キーなのでOK)

ここで重要なのは、

JOIN の片側だけにインデックスがあっても不十分なことがある

という点です。

例:order_items.order_id にインデックスがないとどうなるか

「orders の1行ごとに order_items を全件スキャンする地獄」

orders.id に主キーがあるのは当然ですが、
order_items.order_id にインデックスがないと、JOIN はこう動きます。

orders の1行を読む
order_items を全件スキャンして、order_id が一致する行を探す
次の orders の行でも同じことを繰り返す

つまり、orders の行数 × order_items の行数 という掛け算が発生します。

order_items.order_id にインデックスを貼るだけで、
「order_id = ○○ の行だけを一気に取る」動きに変わり、
JOIN のコストが劇的に下がります。

JOIN の改善で最初にやるべきことは、

JOIN の両側にインデックスがあるか確認する

ということです。


JOIN の順番がパフォーマンスに与える影響

「“どのテーブルから読むか”で rows の桁が変わる」

MySQL は JOIN の順番を自動で最適化しますが、
その判断はインデックスや統計情報に依存します。

EXPLAIN の id / table の順番を見ると、
どのテーブルから読み始めているかが分かります。

例えば、次のような EXPLAIN が出たとします。

idtabletyperows
1usersconst1
1ordersref500
1order_itemsref2000
1productsALL10000

この場合、

users → orders → order_items → products

の順で読み進めています。

ここで問題なのは products が ALL(フルスキャン)になっていることです。
JOIN の順番が悪いというより、products.product_id にインデックスがないのが原因です。

JOIN の順番をいじるよりも、

JOIN の順番が“自然に良くなるように”インデックスを整える

というのが正しいアプローチです。

JOIN の順番を変えると速くなるケース

「WHERE で絞れるテーブルを先に読ませたい」

例えば、次のようなクエリを考えます。

SELECT *
FROM orders o
JOIN users u ON o.user_id = u.id
WHERE u.status = 'active';
Python

users.status にインデックスがある場合、
MySQL は users を先に読む方が効率的です。

しかし、orders.user_id にしかインデックスがない場合、
MySQL は orders を先に読む可能性があります。

このように、

どのテーブルを先に読むべきかは、インデックス次第で変わる

ということを理解しておくと、
JOIN の順番に悩む前に「貼るべきインデックス」が見えてきます。


JOIN を書き換えて速くするテクニック

「JOIN の形を変えるだけで速くなることがある」

JOIN は書き方によってもパフォーマンスが変わります。

INNER JOIN にできるなら INNER JOIN にする

「LEFT JOIN は“残す側”が多いほど重くなる」

例えば、こういうクエリがあります。

SELECT *
FROM orders o
LEFT JOIN users u ON o.user_id = u.id;
Python

しかし、実際には「ユーザーが存在しない注文はない」前提なら、
LEFT JOIN ではなく INNER JOIN で十分です。

SELECT *
FROM orders o
JOIN users u ON o.user_id = u.id;
Python

LEFT JOIN は「結合できない行も残す」ため、
内部処理が増え、最適化も制限されます。

INNER JOIN にできるなら、積極的に変えるべきです。

サブクエリを JOIN に書き換える

「IN (SELECT …) は JOIN より遅くなりやすい」

前半でも触れましたが、次のような書き方は JOIN に書き換えるべきです。

SELECT *
FROM orders
WHERE user_id IN (
  SELECT id FROM users WHERE status = 'active'
);
Python

JOIN にすると、インデックスが効きやすくなります。

SELECT o.*
FROM orders o
JOIN users u ON o.user_id = u.id
WHERE u.status = 'active';
Python

JOIN するテーブルを減らす

「非正規化やキャッシュテーブルでJOINを“そもそも不要にする”」

JOIN が多すぎる場合、
非正規化や集計テーブルを作って JOIN 自体を減らすのも有効です。

例えば、

注文履歴画面で毎回 users と products を JOIN している
→ orders に user_name をコピーする
→ order_items に product_name をコピーする

こうすることで、JOIN を減らし、
クエリをシンプルにできます。

JOIN の改善は、
「JOIN を速くする」だけでなく「JOIN を減らす」方向もある
ということを覚えておいてください。


JOIN の改善を“手順化”する

「この順番で見れば、迷わず改善できる」

JOIN の改善は、次の順番で考えると迷いません。

まず、EXPLAIN でどのテーブルが重いか特定する
次に、JOIN の ON 句に出てくるカラムにインデックスを貼る
WHERE で絞れるテーブルを先に読ませるようにインデックスを整える
LEFT JOIN を INNER JOIN にできないか確認する
サブクエリを JOIN に書き換える
それでも遅いなら、JOIN を減らす(非正規化・集計テーブル)

この順番は、実務でそのまま使える“JOIN 改善の型”です。


Day19 後半のまとめ

JOIN の改善は「インデックス設計」「JOIN 順の理解」「書き方の工夫」の三本柱で成り立ち、特に ON 句に出てくるカラムにインデックスを貼るだけで orders × order_items のような巨大テーブル同士の JOIN が劇的に速くなる。
JOIN の順番は MySQL が自動で決めるが、EXPLAIN の id / table を見れば「どのテーブルから読み始めているか」が分かり、WHERE で強く絞れるテーブルにインデックスを貼ることで“自然に良い順番”を選ばせることができる。
さらに、LEFT JOIN を INNER JOIN に変える、IN (SELECT …) を JOIN に書き換える、JOIN を減らすために非正規化や集計テーブルを導入するなど、書き方の工夫でも大きく改善できるため、「EXPLAIN → インデックス → JOIN の書き方 → JOIN の削減」という順番で改善していくことが、実務で迷わない JOIN チューニングの基本になる。

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