Laravelでリレーションのあるテーブルをwithで取得する【N+1問題】

laravelのwithとは

withはEager loadingを実行するために存在します。

N+1問題とは?それを回避するEager Loadingとは?

  • 遅延ロード: lazy loading
  • Eagerロード: Eager loading

Laravelでは標準では遅延ロードが用いられます。
25件の本があれば、26回クエリが実行されます。

  • booksを取得するクエリ1回
  • authorを取得するクエリ25回

これをN+1問題といいます。クエリを大量に発行するとそれだけ読み込みが遅くなるので問題とされています。

$books = App\Book::all();

foreach ($books as $book) {
    echo $book->author->name;
}
select * from books
select * from authors where id = 1
select * from authors where id = 2
...
select * from authors where id = 25

このようなデータのアクセスの仕方をlazy loadingと言います。

これを回避するためにはwithを使って下記のようにします。

$books = App\Book::with('author')->get();

foreach ($books as $book) {
    echo $book->author->name;
}

これにより発行されるクエリは2回です。

select * from books
select * from authors where id in (1, 2, 3, 4, 5, ...)

このようなデータのアクセスの仕方をeager loadingと言います。

Eloquentリレーションをプロパティとしてアクセスする場合、リレーションのデータは「遅延ロード」されます。つまりプロパティにアクセスされるまで実際にリレーションのデータはロードされることはありません。しかし、Eloquentでは親モデルにクエリする時点で「Eagerロード」できます。EagerローディングはN+1クエリ問題を軽減するために用意しています。たとえばBookモデルがAuthorモデルと関連していると考えてください。

N+1クエリ問題とは

下記URLのような、リレーションのModelでクエリをレコードの数だけ生成してしまうこと。
たとえばUsersとPostsが紐付いていて、N=5件取得しようとすると

-- 1件のクエリ
select * from users limit 5

このクエリ1件と、それぞれのuserごとに発行されるN=5件のクエリが発行されてしまう問題です。

-- N=5件のクエリ
select * from posts where posts.user_id = 1
select * from posts where posts.user_id = 2
select * from posts where posts.user_id = 3
select * from posts where posts.user_id = 4
select * from posts where posts.user_id = 5

参考URL

LaravelでN+1問題 - Qiita
Eloquent:リレーション 5.7 Laravel