Vấn đề N+1
Khi làm việc với Eloquent ORM của Laravel, một trong những "kẻ giết hiệu năng" phổ biến nhất là vấn đề N+1 query. Nó xuất hiện ngay khi bạn load một collection model rồi truy cập một relationship bên trong vòng lặp.
$posts = Post::all(); // 1 query
foreach ($posts as $post) {
echo $post->author->name; // +1 query CHO MỖI post
}
Load 50 posts thì bắn ra 51 query: một cho posts, cộng thêm một cho mỗi author. Lỗi này gần như vô hình khi review code nhưng cực kỳ nguy hiểm khi chịu tải cao.
Cách khắc phục: eager loading với with()
$posts = Post::with('author')->get(); // tổng cộng 2 query
foreach ($posts as $post) {
echo $post->author->name; // không phát sinh query thêm
}
Laravel chạy một query cho posts và một query duy nhất WHERE author_id IN (...) cho tất cả author — luôn là 2 query dù có bao nhiêu post.
Relationship lồng nhau và nhiều relationship
// Nhiều relation
$posts = Post::with(['author', 'comments'])->get();
// Relation lồng nhau (dot notation)
$posts = Post::with('comments.user')->get();
// Ràng buộc relation bằng closure
$posts = Post::with(['comments' => fn ($q) => $q->latest()->limit(5)])->get();
Chỉ load những cột cần thiết
$posts = Post::with('author:id,name')->get();
Chỉ chọn id,name giúp payload nhỏ gọn — nhưng luôn phải bao gồm khóa ngoại (id), nếu không relation sẽ không khớp được.
Eager load sau khi đã có collection
Đã có sẵn collection? Load relationship sau đó bằng load():
$posts = Post::all();
$posts->load('author');
Tự động phát hiện N+1
Bật strict mode trong service provider để lazy loading ném lỗi khi đang phát triển:
use Illuminate\Database\Eloquent\Model;
Model::preventLazyLoading(! app()->isProduction());
Điểm cần nhớ
with()eager-load ngay từ đầu (2 query);load()eager-load một collection đã có sẵn.- Luôn eager-load bất kỳ relationship nào bạn truy cập bên trong vòng lặp.
- Dùng column selection và closure để chỉ load đúng những gì cần.
- Bật
preventLazyLoadingkhi dev để bắt lỗi N+1 trước khi lên production.

