Edited at

LaravelでN+1問題

More than 1 year has passed since last update.

ネストされたリレーションの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を検出させる