LLaravel Bootcamp

Day 4EloquentとリレーションとN+1問題

N+1問題

リレーションを持つデータをループで扱うと、SQLが件数分だけ増える定番の性能問題。

N+1問題とは

問題とは、一覧を取る1本のSQLに加えて、各行の関連データを取るために追加のSQLが件数分(N本)発行されてしまい、合計で「1 + N本」のクエリが走ってしまう性能問題です。Laravelに限らずORM全般で起きる、最も有名な落とし穴のひとつです。

なぜ起きるのか

原因は「遅延ロード」です。リレーションは、実際にアクセスされた瞬間に初めてSQLを発行します。投稿一覧を取り、ループの中で毎回「$post->user->name」のように著者にアクセスすると、投稿の件数だけ著者取得のSQLが繰り返されてしまいます。

悪い例(N+1が発生する)

投稿100件なら、一覧1本 + 著者取得100本 = 合計101本のSQLが発行される。

PHP
// 投稿を全件取得(SQLは1本)
$posts = Post::all();

foreach ($posts as $post) {
    // ループの中で著者にアクセス → 1件ごとにSQLが1本発行される!
    echo $post->user->name;
}
// 投稿100件なら 1 + 100 = 101本のクエリ

良い例(Eager Loadingで解決)

with で著者を事前にまとめて取得。一覧1本 + 著者一括1本 = 合計2本で済む。

PHP
// with('user') で著者を事前にまとめて読み込む([[eager-loading]])
$posts = Post::with('user')->get();

foreach ($posts as $post) {
    // すでに読み込み済みなので、ここでは追加のSQLは出ない
    echo $post->user->name;
}
// 投稿が何件でも 1 + 1 = 2本のクエリで済む

なぜ致命的になりやすいのか

件数が少ないうちは動いてしまうため、開発中は気づきにくいのがN+1の怖さです。本番でデータが増えると、101本・1001本とクエリが膨れ、ページ表示が急に遅くなります。1本1本は速くても、DBへの往復回数そのものが増えるのがボトルネックです。『動くけど遅い』の典型で、レビューや設計段階で潰すべき問題です。

SQL
-- 最初の1本: 一覧
SELECT * FROM posts;

-- 続く N 本: 行ごとに繰り返される
SELECT * FROM users WHERE id = 1;
SELECT * FROM users WHERE id = 2;
SELECT * FROM users WHERE id = 3;
-- … 投稿の件数だけ延々と続く …
実際に発行されるSQL(悪い例のイメージ)
SQL
-- 1本目: 一覧
SELECT * FROM posts;

-- 2本目: 必要な著者を IN でまとめて1回だけ取得
SELECT * FROM users WHERE id IN (1, 2, 3, 4, 5);
Eager Loading後のSQL(良い例のイメージ)

どう気づくか

N+1は「発行されているSQLの本数」を見れば一発で分かります。以下のような方法で、1画面あたり何本のクエリが走っているかを可視化します。

  • Laravel Debugbar: 画面下部に、そのページで発行されたクエリ本数と内容を表示してくれる。同じSQLが大量に並んでいたらN+1のサイン。
  • Laravel Telescope: 開発用の監視ツールで、リクエストごとのクエリを一覧・件数で確認できる。
  • DB::listen やクエリログ: 発行SQLをログに出して数える。
  • Model::preventLazyLoading(): 遅延ロードが起きたら例外を投げさせ、開発中にN+1を強制的に検知する。

補足

コツは「同じ形のSELECTが件数分だけ並んでいないか」を見ることです。WHERE id = ? の値だけ違うSQLが延々と続いていたら、ほぼN+1だと判断できます。

面接ではこう言う

「N+1問題は、一覧の1本に加えて各行の関連取得でN本のSQLが出て、合計1+N本になる性能問題です。遅延ロードのままループ内でリレーションにアクセスすると起きます。私はDebugbarやTelescopeで発行クエリ本数を確認して気づき、withによるEager Loadingで1+1本に抑えて解決します」と、原因・気づき方・解決までワンセットで話せると強いです。

読んでも腑に落ちない所は、AIに噛み砕いてもらいましょう。

理解度チェック

答えられれば、面接で説明できる状態に一歩近づきます。

Q1

N+1問題が起きる直接の原因として、最も適切なものはどれか。

Q2

投稿100件それぞれの著者名を、遅延ロードのまま表示した。発行されるクエリ本数に最も近いものはどれか。

Q3

N+1問題とは何か、なぜ起きるか、どうやって気づくかを、面接官に説明するつもりで述べよ。

記述式です。まず自分の言葉で答えを考えてから、模範解答を開いて自己採点してください。