章: 第3章: データ層の最適化
「一覧画面を開くたびに遅い」「本番だとタイムアウトする」――注意してログを見ると、発行されているSQLが数百件、なんてことがあります。これがN+1問題です。「記事数だけSQLが発行される」状態で、テーブルが増えるほど効果が大きくなります。
Eager loadingと集計関数を組み合わせることで、クエリ数を数百から数件に卻減 できます。一覧画面の遅い問題に直面したとき、まずここを確認しましょう。
なぜEager loadingが重要なのか
N+1問題は開発時には気づきにくく、本番データではじめて昼こります。
Post::all()の後にループで$post->user->nameを呼ぶ――これのみでN+1確定- ページネーションで表示件数が増えるほどクエリ数が増える
withCountなしでコメント数を数えると、記事ごとにSELECT COUNT(*)が発行される
チェックポイント:
with()は取得時に関連を一括取得、load()は取得後に後追ロード。必要な時にwith()を使うのが基本です。
lazyロード vs eagerロード
| 観点 | lazyロード(デフォルト) | with() eagerロード |
| SQL発行数 | N+1(レコード数+1) | 2回固定 |
| メモリ使用 | 少ない | 関連データ分増える |
| 適したシーン | 単一モデル取得時 | 一覧表示・複数データ時 |
| 実務推奨度 | 一覧は齋 | 一覧は必用 |
実装例
<?php
$posts = Post::query()
->with(['user:id,name'])
->withCount('comments')
->latest()
->paginate(20);
foreach ($posts as $post) {
echo $post->user->name . ' (' . $post->comments_count . ')';
}
user:id,name の記法で必要カラムのみ取得。withCount でコメント数をサブクエリで集計するので、全体のクエリは2回に収まります。
まとめ & 次のステップ
with()で関連をEager loadするのがN+1対策の基本user:id,nameのように必要カラムを限定するとメモリと速度の兩方を改善できるwithCount、withSum等の集計メソッドでサブクエリ化してループ内SQLを排除する- Telescopeやデバッグバーで改善前後のクエリ数を比較する習慣をつける
次回は 第08回: Policy/Gateの実践認可 で、認可ルールをコントローラから分離して一貫管理する方法を学びます。