【PHP】N+1問題をLaravel Debugbarを使って検証してみる【Laravel】
N+1問題とは
【Ruby on Rails】N+1問題ってなんだ? - Qiita
1回のクエリ発行でN件のレコードを取得し、
それぞれN件のレコードが持っているリレーション先のテーブルのレコード
を取得しようとする(N回のクエリ発行)とこのN+1問題が起こる。
具体例
実際に以下のようなリレーションを持つテーブルがあったとする。
membersテーブルにて外部キーをclub_idとしてclubsテーブルとリレーションを作る。
テーブルに対応したモデルを作る。
Member.php
<?php namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; class Member extends Model { use HasFactory; public function Club_content() { return $this->hasOne('App\Models\Club','id','club_id'); } }
Club.php
<?php namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; class Club extends Model { use HasFactory; }
ルーティング
<?php use Illuminate\Support\Facades\Route; use App\Http\Controllers\testnplus1; Route::get('/',[testnplus1::class,'index'])->name('test');
そして以下のようなコントローラーとブレードを書いてN+1問題を起こす。
testnplus1.php
<?php namespace App\Http\Controllers; use App\Models\Member; class testnplus1 extends Controller { // public function index() { $members = Member::all(); return view('showname',['members' => $members]); } }
blade
@foreach ($members as $member) <div> <p>{{$member->name}}</p> <p>{{$member->Club_content->name}}</p> </div> @endforeach
そしてlaravel Debugbarを利用してどんなSQLクエリが実行されたか確認してみる。すると以下のような結果となった。
まずコントローラ内のMember::all()はテーブル内のレコードを全て取得するメソッドなので、上記SQLの"select * from members"に対応している。
問題なのはそれ以下のSQLクエリである。ブレードの繰り返し処理内の"$member->Club_content->name"を実行するたびにクエリを実行することとなるのでデータベースに負担をかけてしまうこととなる。今回は5、6個のレコードだけでテストしているが、このレコード数が大きくなればなるほどデータベースへの負荷を高めてしまうこととなる。
このN+1問題を解決するためにイーガーロードを利用する。イーガーロードを利用することでN回行ったSQLクエリを1回のSQLクエリにまとめることができる。
イーガーロードを利用するにはwithメソッドを使用する。
Eloquent:リレーション 8.x Laravel
コントラーのコードを以下のように変更する。
<?php namespace App\Http\Controllers; use App\Models\Member; class testnplus1 extends Controller { // public function index() { //N+1問題が発生するコード //$members = Member::all(); //イーガーローディングによりN+1問題を防ぐコード $members = Member::with('Club_content')->get(); return view('showname',['members' => $members]); } }
Laravel Debugbarを使ってクエリの発行数を見てみると7→2になっている。
これでデータベースへの負荷を下げることができる。
機能実現だけでなく、パフォーマンスのことも考えることが大切だな〜。