#07 Laravel基礎
EagerロードとN+1問題を解決する
N+1問題とは
N+1問題? ループの中で毎回SQLを発行してしまうパフォーマンス問題。10件の投稿を表示するのに11回クエリが走る、などが典型例。 は、リレーションを持つデータを取得するときに起こるパフォーマンス問題です。
よくある例を見てみましょう。
// 📁 app/Http/Controllers/PostController.php
public function index()
{
$posts = Post::all(); // SQL 1回:全投稿を取得
return view('posts.index', compact('posts'));
}
{{-- 📁 resources/views/posts/index.blade.php --}}
@foreach ($posts as $post)
<p>{{ $post->title }} by {{ $post->user->name }}</p>
{{-- ↑ ここで毎回SQL発行! --}}
@endforeach
投稿が10件あれば:
Post::all()で 1回$post->userでループごとに 10回
合計 11回のSQL が実行されます。100件なら101回です。
Eagerロードで解決する
with() を使うと、投稿と投稿者をまとめて2回のSQLで取得できます。
// 📁 app/Http/Controllers/PostController.php
public function index()
{
// with('user') で user リレーションを先読み(Eager Load)
$posts = Post::with('user')->get();
return view('posts.index', compact('posts'));
}
実行されるSQL:
-- 1本目:全投稿を取得
SELECT * FROM posts;
-- 2本目:関連ユーザーをまとめて取得(WHERE IN で一括)
SELECT * FROM users WHERE id IN (1, 2, 3, ...);
何件あっても常に2回だけです。
複数のリレーションをまとめてEagerロード
// 📁 app/Http/Controllers/PostController.php
// user(作者)とtags(タグ)を同時にEagerロード
$posts = Post::with(['user', 'tags'])->get();
ネストしたリレーションもEagerロードできる
「投稿」→「コメント」→「コメントの作者」のようなネストも対応できます。
// posts → comments → user を全部まとめて取得
$posts = Post::with('comments.user')->get();
Eagerロードに条件を付ける
with() にクロージャーを渡すと、先読みするデータを絞り込めます。
// 📁 app/Http/Controllers/PostController.php
// 公開済みコメントだけをEagerロード
$posts = Post::with(['comments' => function ($query) {
$query->where('status', 'approved')->latest();
}])->get();
lazyEagerLoad:後から Eager Load する
すでに取得済みのコレクションに後からEagerロードしたい場合は loadMissing() を使います。
$posts = Post::all();
// 後からuserリレーションを読み込む
$posts->loadMissing('user');
N+1を発見するには
開発中は preventLazyLoading() を使うと、Eagerロードしていないリレーションへのアクセスを検知できます。
// 📁 app/Providers/AppServiceProvider.php
use Illuminate\Database\Eloquent\Model;
public function boot(): void
{
// 本番環境以外でN+1を検知する
Model::preventLazyLoading(! app()->isProduction());
}
これを設定すると、$post->user をEagerロードせずにアクセスした場合に LazyLoadingViolationException がスローされます。
まとめ
- N+1問題:ループ内でリレーションにアクセスするたびにSQLが1本発行される問題
with('リレーション名')でEagerロードすると2本のSQLで解決できるwith(['user', 'tags'])で複数リレーションをまとめてロードできる- ネストは
with('comments.user')のように.で繋げる Model::preventLazyLoading()で開発中にN+1を検知できる
次回はEloquentのスコープとアクセサーを学びます。