Eloquent Performance - N+1 Query Problems - Laravel

morcosgad

Morcos Gad

Posted on May 23, 2022

Eloquent Performance - N+1 Query Problems - Laravel

I knew that this problem N+1 is one of the problems that affect the performance of my project and I found this article that contains the reasons https://laravel-news.com/laravel-n1-query-problems and solution to this problem and I wanted to share it with you because of the importance of this topic

Four cases to clarify the problem and solve it

  • Case 1. "Regular" N+1 Query
// app/Models/Book.php
class Book extends Model
{
    public function author()
    {
        return $this->belongsTo(Author::class);
    }
}
// Then, in some Controller:
$books = Book::all();
foreach ($books as $book) {
    echo $book->author->name;
}
Enter fullscreen mode Exit fullscreen mode

fix

// Instead of
$books = Book::all();
// You should do
$books = Book::with('author')->get();
Enter fullscreen mode Exit fullscreen mode
  • Case 2. Two Important Symbols
public function index()
{
    $authors = Author::with('books')->get();
    return view('authors.index', compact('authors'));
}

// Blade
@foreach($authors as $author)
    <tr>
        <td>{{ $author->name }}</td>
        <td>{{ $author->books()->count() }}</td>
    </tr>
@endforeach
Enter fullscreen mode Exit fullscreen mode

So, the method of relation would query the database for each author. But if you load the data, without () symbols, it will successfully use the eager loaded data $author->books
fix

// Controller
$authors = Author::withCount('books')->get();
// Blade
{{ $author->books_count }}
Enter fullscreen mode Exit fullscreen mode
  • Case 3. "Hidden" Relationship in Accessor
// Controller
public function index()
{
    $authors = Author::all();
    return view('authors.index', compact('authors'));
}

// Blade
@foreach($authors as $author)
    <tr>
        <td>{{ $author->name }}</td>
        <td>{{ $author->is_active ? 'Yes' : 'No' }}</td>
    </tr>
@endforeach
Enter fullscreen mode Exit fullscreen mode

That "is_active" is defined in the Eloquent model

use Illuminate\Database\Eloquent\Casts\Attribute;
class Author extends Model
{
    public function isActive(): Attribute
    {
        return Attribute::make(
            get: fn () => $this->books->count() > 0,
        );
    }
}
Enter fullscreen mode Exit fullscreen mode
  • Case 4. Be Careful with Packages

Laravel has a great ecosystem of packages, but sometimes it's dangerous to use their features "blindly". You can run into unexpected N+1 queries if you're not careful.

  • The Built-In Solution Against N+1 Query

since Laravel 8.43, the framework https://laravel-news.com/disable-eloquent-lazy-loading-during-development

In addition to the Laravel Debugbar for inspection, you can add a code to the prevention of this problem

You need to add two lines of code to app/Providers/AppServiceProvider.php

use Illuminate\Database\Eloquent\Model;
class AppServiceProvider extends ServiceProvider
{
    public function boot()
    {
        Model::preventLazyLoading(! app()->isProduction());
    }
}
Enter fullscreen mode Exit fullscreen mode

Now, if you launch any page that contains an N+1 Query problem, you will see an error page

I have explained only the important points, but to go deeper, the article must be increased and benefited from.

💖 💪 🙅 🚩
morcosgad
Morcos Gad

Posted on May 23, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related