第07回: Eloquent最適化(N+1/集計) — 一覧画面が遅いのは、実はN+1問題のせいです

章: 第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 のように必要カラムを限定するとメモリと速度の兩方を改善できる
  • withCountwithSum 等の集計メソッドでサブクエリ化してループ内SQLを排除する
  • Telescopeやデバッグバーで改善前後のクエリ数を比較する習慣をつける

次回は 第08回: Policy/Gateの実践認可 で、認可ルールをコントローラから分離して一貫管理する方法を学びます。

Related Articles