Giải quyết vấn đề N+1 Query trong Laravel Eloquent

· 2 min read

Eloquent rất đẹp, nhưng rất dễ vô tình giết hiệu năng server của bạn với vấn đề N+1 query.

N+1 là gì?

Nó xảy ra khi bạn lặp qua một collection và chạy một query cho mỗi item.

Code:

$posts = Post::all(); // 1 Query

foreach ($posts as $post) {
    echo $post->user->name; // N Queries (một cho mỗi post)
}

Nếu bạn có 100 posts, bạn vừa chạy 101 queries. Nếu bạn có 1000 posts, bạn chạy 1001 queries.

Giải pháp: Eager Loading (with)

Bảo Eloquent load relationship trước.

$posts = Post::with('user')->get(); // Tổng cộng 2 Queries

Query 1: SELECT * FROM posts Query 2: SELECT * FROM users WHERE id IN (1, 2, 3...)

Eloquent sau đó ghép chúng lại với nhau trong bộ nhớ.

Lazy Eager Loading (load)

Đôi khi bạn đã có parent model (có thể từ route binding), và bạn quyết định sau đó cần relationships.

public function show(Post $post)
{
    $post->load(['user', 'comments']);
    
    return view('posts.show', compact('post'));
}

Nested Relationships

Bạn có thể load các cây sâu.

// Load Post -> Comments -> Author của Comment
$posts = Post::with('comments.user')->get();

Làm thế nào để ngăn N+1 vĩnh viễn?

Từ Laravel 8, bạn có thể tắt lazy loading hoàn toàn trong development.

AppServiceProvider.php

public function boot()
{
    Model::preventLazyLoading(! app()->isProduction());
}

Bây giờ, nếu bạn cố truy cập $post->user mà không load nó trước, Laravel sẽ throw một Exception thay vì âm thầm chạy query. Điều này buộc bạn viết code hiệu quả trong quá trình development.

Kết luận

N+1 queries là những kẻ giết thầm lặng. Chúng hoạt động tốt với 10 bản ghi ở local, nhưng crash production với 10,000 bản ghi. Luôn sử dụng with() hoặc load().

Bình luận