Laravel 8.43.0 の Preventing Lazy Loading 機能で、N+1問題を早めに発見してみる
はじめに
Laravel 8.43.0 の目玉機能の「Preventing Lazy Loading」を試してみました。
これは何かと言うと、Eloquent Model で Lazy Loading している場合、エラーにしてしまうという機能です。そうすることで、DBの N+1 問題を起こしている箇所を早めに見つけようという話です。
本家ドキュメント:Preventing Lazy Loading
早速試す
まず、AppServiceProvider の boot() などで、以下のように記述して、この機能を有効にします。
use Illuminate\Database\Eloquent\Model;
public function boot()
{
Model::preventLazyLoading(! $this->app->isProduction());
}
引数は、この機能を有効にする為の条件で、上記の場合、productionサーバだったら無効にするという事です。
以降では、Post belongsTo User と想定し、Postモデルには以下のメソッドがあるとします。
class Post extends Model
{
public function user()
{
return $this->belongsTo(User::class);
}
}
後はサクッと試してみます。以下のように記述します。
$posts = Post::get();
foreach ($posts as $post) {
$post->user->name; // lazy load 発生!
}
これで、
Illuminate\Database\LazyLoadingViolationException
Attempted to lazy load [user] on model [App\Models\Post] but lazy loading is disabled.
という感じのエラーが表示されて処理がストップします。
もしこの機能をオンにしていなければ、N+1問題が発生していた所です。
では、下記のように書き換えると、
$posts = Post::with('user')->get(); // Eagerロード
foreach ($posts as $post) {
$post->user->name;
}
問題無く処理が実行されます。N+1問題も発生しません。
で、注意点や制限事項は?
lazy load を指摘してくれますが、動的プロパティを介さず、リレーションメソッドを直接呼び出している場合には、指摘してくれません。
例えば、
$posts = Post::get();
foreach ($posts as $post) {
$post->user()->first()->name;
}
結局これも lazy load なのですが、こちらの場合はエラーになりません。
ですが、N+1問題は、発生しています。
(追記)以下は、8.44.0 で修正されましたので、エラーとならなくなります。
もう1つ。
複数のモデルを取得している訳では無く、1つだけだとしても、動的プロパティで lazy load していれば、エラーとなります。下記のような場合です。
$post = Post::first();
$post->user->name;
この場合、N+1問題は関係ないですが、エラーとなっていまいます。
回避したければ、素直に ::with('user') するか、又はメソッド形式で呼び出すしかないですね。
技術的には下記も可能ですが、まぁ普通はしないでしょう。~
$post = Post::first();
$post->preventsLazyLoading = false;
$post->user->name;
雑感
便利と言えば便利ですね。
まぁ、たまには?Lazy Loading は、Lazy Loading なりの使い所もあるでしょうから、その辺は要調整といった感じでしょうか。
おかしな箇所等ありましたら、コメント下さい。
Discussion