The N+1 query problem is a common performance issue in Laravel’s Eloquent ORM. It occurs when querying relational data inefficiently. In this post, we’ll explore this problem using a detailed example and demonstrate how to solve it with eager loading.
Scenario Overview
Imagine you have a Laravel application that manages social media posts. The application stores account credentials for platforms like Facebook, Instagram, and X, and it also schedules content to be posted on these platforms. Here’s a breakdown of the data:
Entities
- SocialMediaAccount: Stores credentials for Facebook, Instagram, and X.
- Post: Stores content to be posted along with the scheduled date and time.
Relationships
- A
SocialMediaAccount
has manyPost
s (One-to-Many relationship). - A
Post
belongs to aSocialMediaAccount
.
Example Data
Let’s consider the following example data:
SocialMediaAccount Table
id | platform | username | password | access_token | refresh_token | expires_at |
---|---|---|---|---|---|---|
1 | user1_fb | secret1 | token_fb | refresh_fb | 2024-09-30 00:00:00 | |
2 | user2_ig | secret2 | token_ig | refresh_ig | 2024-09-30 00:00:00 | |
3 | X | user3_x | secret3 | token_x | refresh_x | 2024-09-30 00:00:00 |
Post Table
id | social_media_account_id | content | scheduled_at | posted_at |
---|---|---|---|---|
1 | 1 | Post on FB 1 | 2024-08-30 09:00:00 | NULL |
2 | 2 | Post on IG 1 | 2024-08-30 10:00:00 | NULL |
3 | 1 | Post on FB 2 | 2024-08-30 11:00:00 | NULL |
4 | 3 | Post on X 1 | 2024-08-30 12:00:00 | NULL |
The N+1 Query Problem Explained
Let’s suppose today is 2024-08-30. You want to fetch all posts scheduled for today and post them on the respective social media platforms.
1. Fetch All Posts Scheduled for Today
$posts = Post::whereDate('scheduled_at', today())->get();
SQL Query Executed:
SELECT * FROM posts WHERE DATE(scheduled_at) = '2024-08-30';
This query fetches the following posts:
- Post 1:
id = 1
,social_media_account_id = 1
,content = "Post on FB 1"
- Post 2:
id = 2
,social_media_account_id = 2
,content = "Post on IG 1"
- Post 3:
id = 3
,social_media_account_id = 1
,content = "Post on FB 2"
- Post 4:
id = 4
,social_media_account_id = 3
,content = "Post on X 1"
2. Loop Through Posts and Fetch Related Social Media Accounts
foreach ($posts as $post) {
$account = $post->socialMediaAccount;
// Post content to the social media platform
}
SQL Queries Executed:
- For Post 1: Fetch
SocialMediaAccount
withid = 1
- For Post 2: Fetch
SocialMediaAccount
withid = 2
- For Post 3: Fetch
SocialMediaAccount
withid = 1
(Duplicate query) - For Post 4: Fetch
SocialMediaAccount
withid = 3
Total Number of Queries: 1 query to fetch posts + 4 queries to fetch related accounts = 5 queries.
Problem: Two of these queries are duplicates, fetching the same account (id = 1
) twice. This is inefficient, especially as the number of posts grows.
Solving the N+1 Query Problem with Eager Loading
To optimize this, you can use eager loading to fetch all related SocialMediaAccount
s in a single query.
1. Eager Load Social Media Accounts
$posts = Post::with('socialMediaAccount')->whereDate('scheduled_at', today())->get();
SQL Queries Executed:
- Fetch Posts
- Fetch All Related
SocialMediaAccount
s in One Query
2. Accessing Related Data
foreach ($posts as $post) {
$account = $post->socialMediaAccount;
// Post content to the social media platform
}
Total Number of Queries:
With eager loading, the number of queries is reduced to 2:
- One query to fetch all posts.
- One query to fetch all related social media accounts (no duplicates).
Detailed Benefits with Dummy Data
- Before Eager Loading:
- Number of Posts: 4
- Number of Queries: 5 (including 2 duplicate queries)
- After Eager Loading:
- Number of Posts: 4
- Number of Queries: 2
Conclusion
In this example, eager loading reduced the number of database queries from 5 to 2, eliminating duplicate queries and significantly improving performance. As the number of posts and related data grows, optimizing queries with eager loading in Laravel applications using Eloquent ORM becomes increasingly important.
Leave a Reply