いつも通りコントローラーでリレーション使いながらお仕事をしていた時。
view側を見ると
こんなコードが
<tr>
<th colspan="4" rowspan="2">果物を収穫した農園主</th>
<th colspan="2">農園の所在地</th>
<td colspan="8">{{ $fruit->field->owner->region }}{{ $fruit->field->owner->farm_address }}</td>
</tr>
コントローラーを見ると
$fruit = Work::where('orchard_id', $orchard->id)
->whereNull('rotten_at')
->with('branch')
->with('field')
->with('basket')
->with('basket.seed')
->with('picker')
->with('customer')
->findOrFail($fruitId);
withでとってきていないな。
ん?これでも取得できるんだ「へー」となっておりました。
損で調べてみたら
N+1問題を回避するためにはコントローラーでまとめたほうが良いよ!
と書いてある。
何じゃ?
ということでもうちょっと調べてみた。
N+1ってまずなに
パターン紹介
ユーザー一覧を取得する。
SELECT * FROM users;
→100人いました!!!
その後各ユーザーの投稿件数を数えるためには100回クエリの実行が必要になる。
SELECT * FROM posts WHERE user_id = 1;
SELECT * FROM posts WHERE user_id = 2;
...
SELECT * FROM posts WHERE user_id = 100;
結果
合計ユーザー一覧で1回、ユーザーごとの投稿数を取得するのに100回のSQLを実行した。
欲しいのはユーザーごと(100人分)の投稿数。
クエリの実行が合計101回となるということでN+1問題ということらしい。
この数が多くなればなるほどどんどんパフォーマンスが落ちるよ〜ということ。
それを解決するのがいつもコントローラーでやっていたあれ。
$users = User::with('posts')->get();
これでユーザーと投稿をひとまとめで取得できる。
ユーザーを取得するクエリとこのクエリの合計2回で済むってことらしい。
このwithメソッドはEager loadingというらしい。
SQL文を利用すると
$users = User::select('users.*')
->leftJoin('posts', 'users.id', '=', 'posts.user_id')
->groupBy('users.id')
->selectRaw('count(posts.id) as post_count')
->get();
つまりviewに記載していたのはクエリを余分に実行してしまっていた。
なので
$fruit = Work::where('orchard_id', $orchard->id)
->whereNull('rotten_at')
->with('branch')
->with('field')
->with('basket')
->with('basket.seed')
->with('picker')
->with('customer')
->with('owner') //追加
->findOrFail($fruitId);
としてこう書き換えた
<tr>
<th colspan="4" rowspan="2">果物を収穫した農園主</th>
<th colspan="2">農園の所在地</th>
<td colspan="8">{{ $fruit->owner->region }}{{ $fruit->owner->farm_address }}</td>
</tr>
これでクエリの実行回数は減ったんだろう。
ローカルの数が少ないところでやっているので速度はわからないが。
クエリって何?
クエリクエリ言ってなんとなく使っていたけど
クエリはDBに質問する命令文のことを言うらしい。
SELECTやINSERT、UPDATE、DELETEのことだ。
機能の追加作業をやっているけど楽しいな。
そしてこういった要件決める人大変そう。