🌀

N + 1 問題 is 何

2024/12/29に公開

DB のデータを扱っているときに発生する N + 1 問題についての解説.

N + 1 問題

データを取得する際のコードの書き方が問題となって発生する事象「N + 1 問題」がある.

何が起きているのか

この問題は hasManybelongsTo を用いた連携をしている場合に発生する.

例えば,下記のようにユーザが投稿した tweet の一覧を取得する場合を考える.

$tweets = Tweet::query()
  ->where('user_id', auth()->id())
  ->get();

また,ビューファイルでは投稿したユーザ名を取得するため,下記のコードが実行されていることとする.

@foreach($tweets as $tweet)
...
{{$tweet->user->name}}
...
@endforeach

この場合,大雑把に言うと「tweet の一覧を取得する SQL 文が 1 回」「各 tweet のユーザ情報を取得するための SQL 文が tweet の個数分」実行されてしまう.

つまり,tweet が 1000 件存在したら 1001 回の SQL 文が実行されることとなる.データ件数が多いとわりとヤバい.

解決法

解決には「Eager ロード」を用いる.

with メソッドを使用して予めリレーションしているデータを一括して取得することができる.こうすることで tweet の個数分 SQL を実行しなくてよくなる.

$tweets = Tweet::query()
  ->where('user_id', auth()->id())
  ->with(['user'])
  ->get();

ちなみに,show メソッドなどで Tweet のデータが直接渡されている場合は load メソッドを使用する.

$tweet->load(['user']);

起きているのか起きていないのかわからん

下記の方法で調査できる.

AppServiceProvider.php に以下のようにコードを追加するだけ.

// app/Providers/AppServiceProvider.php

<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
// 🔽 追加
use Illuminate\Database\Eloquent\Model;

class AppServiceProvider extends ServiceProvider
{
  public function register()
  {
    //
  }

  public function boot()
  {
    // 🔽 追加
    Model::preventLazyLoading(!$this->app->isProduction());
  }
}


あとはアプリケーションを動かすと,問題のある箇所で以下のようなエラーが表示される.

Attempted to lazy load [hoge] on model [App\Models\Hoge] but lazy loading is disabled.

コードの中で発生している部分も表示してくれるため,with メソッドなどで修正 → 動作確認してエラーが表示されなくなれば解消されている.

以上だ( `・ω・)b

GitHubで編集を提案

Discussion