🌀
N + 1 問題 is 何
DB のデータを扱っているときに発生する N + 1 問題についての解説.
N + 1 問題
データを取得する際のコードの書き方が問題となって発生する事象「N + 1 問題」がある.
何が起きているのか
この問題は hasMany
や belongsTo
を用いた連携をしている場合に発生する.
例えば,下記のようにユーザが投稿した 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
Discussion