#前書き
バックエンドたるものN+1問題しらんくてどうする??っと言われたので、少し調べてみました。
正直、私のコードだめだめやん。って思いました。😭
#N+1問題とは??
リレーションで繋がってるテーブル間でのデータを取得し、大抵の場合foreachでデータを回すと思うのですが。
データの取得方法が悪いと、foreachで回す度にクエリが発生しちゃうっていう問題です。(大ざっぱにいうと。)
###クエリが多いと何が問題なのか?
クリエの発行数が多いとサーバーの負荷
になっちゃって反応遅延
や、最悪の場合にはシステムダウン
につながる由々しき問題なのです!
LaravelだとEloquentっていうものがある分、実際に発行されているクエリが見えないのでそこら辺が可視化されにくくなってます。
便利なものも考えものですな。
#実際に発行されるクエリを確認する方法。
下記のテーブルがあたっとします。
>>Users table
id |name|
>>Bbses table
id | body |user_id |
>>Comments table
id | comment | bbs_id |
※あとでUserテーブルからデータをとってforeachで回します。
その時のリレーションのイメージは [users->bbses->comments] ってな感じです。
たとえば、Userテーブルから5件データを取得する。
public function index()
{
$users = User::limit(5)->get();
return view('user.index')->with('users', $users);
}
次に、viewでforeachを使って$users を回す。
@foreach ($users as $user)
<p>{{ $user->name }}</p>
<ul>
@foreach ($user->bbses as $bbs)
<li>{{ $bbs->body }}</li>
<li>コメント数:{{ count($bbs->comments) }}</li>
@endforeach
</ul>
@endforeach
すると下記のようなクリエが発行される。
select * from users limit 5
select * from bbses where bbses.user_id = 1
select * from comments where comments.bbs_id = 1
select * from bbses where bbses.user_id = 2
select * from comments where comments.bbs_id = 2
select * from bbses where bbses.user_id = 3
select * from comments where comments.bbs_id = 3
select * from bbses where bbses.user_id = 4
select * from comments where comments.bbs_id = 4
select * from bbses where bbses.user_id = 5
select * from comments where comments.bbs_id = 5
$usersからリレーションで繋がっている情報が取得されるたびにクリエが発行されていることがわかる。
このクリエの発行件数を少なくするために使用するのがwith
である。
#withを使ってクリエの発行回数を少なくしよう。
public function index()
{
$users = User::with('bbses')->limit(5)->get();
return view('user.index')->with('users', $users);
}
select * from users limit 5
select * from bbses where bbses.user_id in (1, 2, 3, 4, 5)
select * from comments where comments.bbs_id = 1
select * from comments where comments.bbs_id = 2
select * from comments where comments.bbs_id = 3
select * from comments where comments.bbs_id = 4
select * from comments where comments.bbs_id = 5
う〜ん。なにやら、commentsテーブルの方がまだ多い。
こんな時は、.(ドット)
を使う!
public function index()
{
$users = User::with('bbses.comments')->limit(5)->get();
return view('user.index')->with('users', $users);
}
select * from users limit 5
select * from bbses where bbses.user_id in (1, 2, 3, 4, 5)
select * from comments where comments.bbs_id in (1, 2, 3, 4, 5)
#ちなみにLaravelでのSQLの確認方法
$sql = DB::table('users')
->where('id', 1)
->toSql();
var_dump($sql);
exit();
他にも👇でもおっけ〜です。
$sql = User::->where('id', 1)->toSql();
var_dump($sql);
exit();
上記みたいな感じで書くと確認できます!
残念なことにall();
ではtoSql();
が使えません😭
この事実にかなりショックを受けました。
N+1問題のことを調べましたが、まだまだひよこ故理解が不十分なので。
なにか間違っている点などありましたら、編集リクエストお願いします😌