Laravel 11 - N+1 Problem - Lazy vs Eager Loading

Touseef Afridi
22 Oct 24

Laravel 11 - N+1 Problem - Lazy vs Eager Loading

In this tutorial, we will discuss the N+1 problem in Laravel 11 and explore its implications for query performance and application efficiency.


If you're a video person, feel free to skip the post and check out the video instead!


We will explore the N+1 problem in Laravel 11 through practical examples. We'll walk through the process of creating a Post model, defining relationships, and observing how lazy loading can lead to excessive queries. Then, we'll see how eager loading can optimize our queries effectively. Let’s dive in!

Step # 1 : Create Post Model, Migration & Factory.

By default, Laravel provides the User model, migration, and factory, so we don't need to create them manually.
Run the following command to generate a Post model, migration file, and factory in one go.
Command : php artisan make:model Post -mf

Step # 2 : Update Post Migration.

In the create_posts_table.php migration file, you need to define the necessary fields and set up a foreign key to reference the user.
Here's how the up() method should look.
public function up()
{
    Schema::create('posts', function (Blueprint $table) {
        $table->id();
        $table->foreignId('user_id')->constrained()->onDelete('cascade'); // Foreign key for user
        $table->string('title');
        $table->text('content');
        $table->timestamps();
    });
}
This configuration creates a posts table with an id, user_id (as a foreign key), title, content, and timestamps, ensuring that each post is associated with a user.

Step # 3 : Run the Migration.

Execute the following command to apply the migration.
Command : php artisan migrate

Step # 4 : Define Relationships.

You need to create the following methods to define the relationships between the User and Post models.
In the User model (User.php)
public function posts() // A user can have many posts
{
    return $this->hasMany(Post::class);
}
In the Post model (Post.php)
public function user() // Each post belongs to a user
{
    return $this->belongsTo(User::class);
}
This setup establishes the one-to-many relationship where a user can have multiple posts, and each post is associated with a single user.

Step # 5 : Update PostFactory.php.

Update the PostFactory.php like below to create fake records.
<?php
namespace Database\Factories;
use App\Models\Post;
use Illuminate\Database\Eloquent\Factories\Factory;
class PostFactory extends Factory
{
    protected $model = Post::class;
    public function definition()
    {
        return [
            'title' => $this->faker->sentence,
            'content' => $this->faker->paragraph,
        ];
    }
}
This configuration allows the factory to generate random titles and content for posts using Faker when seeding the database.

Step # 6 : Update DatabaseSeeder.php.

You need to modify the DatabaseSeeder.php file to seed your database with users and their associated posts. Here’s how to do it.
<?php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
use App\Models\User;
use App\Models\Post;
class DatabaseSeeder extends Seeder
{
    public function run(): void
    {
        // Create 10 users and for each user, create 3 posts
        User::factory(10)->create()->each(function ($user) {
            Post::factory(5)->create(['user_id' => $user->id]); // Associate posts with user
        });
    }
}

Step # 7 : Run the Seeder.

To populate your database with the data defined in your seeder, execute the following command.
Command : php artisan db:seed

Step # 8 : Let's See N+1 Problem in action.

Start the Laravel development server.
Command : php artisan serve.
Access below URL
127.0.0.1:8000
I’m using the Laravel Debugbar tool to monitor how many queries are being executed.

Lazy Load:
Let's update the route with lazy loading to demonstrate the N+1 problem in action.
Route::get('/', function () {
    $users = User::all(); // 1 query to fetch all users
    foreach ($users as $user) {
        echo $user->posts; // N queries to fetch posts for each user on demand (N = number of users)
    }
    return view('welcome');
});
Number of Queries executed. Refer to the image below.

Eager Loading:
By using eager loading, we can resolve the N+1 problem by fetching all users and their posts in a single query.
Route::get('/', function () {
    $users = User::with('posts')->get(); // **1 query to fetch all users and their posts**
    foreach ($users as $user) {
        echo $user->posts; // No additional queries are made for posts
    }
    return view('welcome');
});
Number of Queries executed. Refer to the image below.

Use lazy loading for infrequent access to related data to optimize resource usage, while eager loading is best when you need related data upfront to improve performance and prevent the N+1 query problem.

Share this with friends!


"Give this post some love and slap that 💖 button as if it owes you money! 💸😄"
0

0 Comments

To engage in commentary, kindly proceed by logging in or registering