はじめに
Laravelではリレーション定義を行ったモデルを使用することでN+1問題が発生します。
今回はN+1問題を解決するためにwithメソッド
を利用したので、備忘録として残しておきます。
環境
- Laravel:8.83.4
1. withメソッドとは
取得したModelインスタンスのリレーション先を事前に取得するメソッドです。
「Eagerロード」によってN+1問題を解決するために利用されます。
2. N+1問題を解決する
今回はUserモデルに以下のようなリレーションを定義して進めていきます。
Userモデル
<?php
namespace App\Models;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;
class User extends Authenticatable
{
//略
/**
* リレーション - postsテーブル
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function posts(): HasMany
{
return $this->hasMany(Post::class);
}
//略
}
まずはwithメソッド
を使わずにデータを取得する場合から見ていきます。
2-1. withメソッドを使わない場合
全ユーザーを取得した後に、動的プロパティ
でユーザーに紐づく投稿記事をそれぞれ取得しています。
<?php
namespace App\Http\Controllers;
use App\Models\User;
class UserController extends Controller
{
public function index()
{
$users = User::all();
foreach ($users as $user) {
$user->posts;
}
}
}
動的プロパティ
にはリレーション先のプロパティが参照されるまでロードされないという性質があります。
そのため、今回の場合でいうと、ユーザーの数が増えるにつれてクエリの発行回数も増えていきます。
このようなN+1問題によるパフォーマンスの低下を招かないための対策としてwithメソッド
を利用します。
2-1. withメソッドを使う場合
withメソッド
でEagerロードすることでN+1問題を解決します。
<?php
namespace App\Http\Controllers;
use App\Models\User;
class UserController extends Controller
{
public function index()
{
$users = User::with('posts')->get();//変更
foreach ($users as $user) {
$user->posts;
}
}
}
今回の場合、ユーザー情報だけでなく投稿記事も一括で取得することでクエリの発行回数を減らしています。
withメソッド
を使うことで、ユーザーの数が増えても、
usersテーブル
のデータを取得するクエリとpostsテーブル
のデータを取得するクエリ、計2回のクエリしか発行されません。
このようにして事前にリレーション先のデータを取得することでN+1問題を解決することができます。
以上で実装完了です。
N+1問題はWebシステムのパフォーマンス悪化につながるので、普段から意識しながらコードを書いていきたいです。
3. 参考文献