📌
【laravel】N+1問題に気をつけろ
はじめに
アプリ開発中にN+1問題について度々指摘されてしまったので頭の中の整理と自分への戒めを込めて投稿
N+1を起こしていたコード
class Project extends Model
{
// 略
public function team()
{
return $this->belongsTo(Team::class);
}
public function user()
{
return $this->belongsTo(User::class);
}
public function tasks()
{
return $this->hasMany(Task::class);
}
// 略
}
上記のProject Modelのリレーションを以下のように使用
public static function getGanttData(User $user): array
{
$current_team = $user->selectedTeam;
$projects = $current_team->projects;
$ganttData = [];
foreach ($projects as $project) {
$projectData = static::processProject($project);
$ganttData[] = $projectData;
}
return $ganttData;
}
private static function processProject($project): array
{
$tasks = $project->tasks->sortBy('start_date');
$projectData = [
'id' => "project-{$project->id}",
'name' => $project->name,
'start' => '',
'end' => '',
'progress' => 0,
'dependencies' => null,
'user_id' => $project->user_id,
'user_name' => $project->user->name,
];
$processedTasks = static::processTasks($tasks, $project);
if ($processedTasks->isNotEmpty()) {
$projectData['start'] = $processedTasks->min('start');
$projectData['end'] = $processedTasks->max('end');
}
return array_merge($projectData, ['tasks' => $processedTasks->toArray()]);
}
N+1問題を起こしている箇所
private static function processProject($project): array
{
$tasks = $project->tasks->sortBy('start_date');
// 略
↑それぞれの$project
が持っているtasks
を取得しようとしている
//略
'dependencies' => null,
'user_id' => $project->user_id,
'user_name' => $project->user->name,
];
↑同じくそれぞれの$project
が持っているuser
を取得しようとしている
このように、1回のクエリ発行でN件のレコードを取得し、それぞれN件のレコードが持っているリレーション先のテーブルのレコードを取得しようとする(N回のクエリ発行)とN+1問題が起こる。
解決手段
イーガーローディングを利用する
laravelではwith()
メソッドを使用してイーガーローディングを実装することができる
具体的な修正コード
$projects = $current_team->projects()->with(['tasks' => function($query) {
$query->orderBy('start_date');
}, 'user'])->get();
with()
を使用してプロジェクト、タスク、およびユーザー情報を一度のクエリでプリロードすることで後続の
private static function processProject($project): array
{
$tasks = $project->tasks;
// 略
や
'user' => $project->name,
でクエリを発行することなく処理することができる。
N+1問題に気付くために
-
ループ内でのデータベースクエリ:
コード内でループを使用している箇所、特にコレクションや配列をイテレートしている部分で、各イテレーション内でデータベースクエリが実行されていないか確認する。 -
リレーション参照の方法:
モデル間のリレーションを参照する際、特に$model->relationのような形式で直接アクセスしている箇所に注意。 -
ツールの使用:
laravel Debugbar
のようなデバッガーツールを使用してどんなクエリが発行されているかチェックする
参考記事
Discussion