PHP
laravel
laravel5

LaravelでN+1問題

ネストされたリレーションのN+1問題を回避したいので調査した。

Laravel:5.6.26

例としてERとLaravelのコード

ER

UserController.php
public function index()
{
    $users = User::limit(5)->get();
    return view('user.index')->with('users', $users);
}
index.blade.php
@foreach ($users as $user)  
<p>{{ $user->name }}</p>
<ul>
  @foreach ($user->posts as $post)
  <li>{{ $post->title }}</li>
  <li>コメント数{{ count($post->comments) }}</li>
  @endforeach
</ul>
@endforeach

結果、下記の様なselect文が発行される

select * from users limit 5
select * from posts where posts.user_id = 1
select * from comments where comments.post_id = 10
select * from posts where posts.user_id = 2
select * from comments where comments.post_id = 20
select * from posts where posts.user_id = 3
select * from comments where comments.post_id = 30
select * from posts where posts.user_id = 4
select * from comments where comments.post_id = 40
select * from posts where posts.user_id = 5
select * from comments where comments.post_id = 50

N+1問題を回避するためにwithを使う

UserController.php
public function index()
{
    $users = User::with('posts')->limit(5)->get();
    return view('user.index')->with('users', $users);
}
select * from users limit 5
select * from posts where posts.user_id in (1, 2, 3, 4, 5)
select * from comments where comments.post_id = 10
select * from comments where comments.post_id = 20
select * from comments where comments.post_id = 30
select * from comments where comments.post_id = 40
select * from comments where comments.post_id = 50

postsは回避されたがcommentsがまだ

withでドット指定

UserController.php
public function index()
{
    $users = User::with('posts.comments')->limit(5)->get();
    return view('user.index')->with('users', $users);
}
select * from users limit 5
select * from posts where posts.user_id in (1, 2, 3, 4, 5)
select * from comments where comments.post_id in (10, 20, 30, 40, 50)

ネストされたリレーション先のcommentsまで対応できる

RailsのbulletみたいなやつがLaravelにあるといいんだけど無いっぽい。

※プロダクトのコードで試した為に上記コードは記事作成時に手で起こしてあります。コピペしても動かないかも

追記

N+1が発生しているか、発見できるLaravel N+1 Query Detectorってライブラリがあったようです
LaravelでN+1を検出させる