
N+1 Query là gì và vì sao nguy hiểm?
N+1 xảy ra khi:
- Bạn chạy 1 query chính
- Sau đó chạy thêm N query phụ cho từng record
Ví dụ:
$users = User::all();foreach ($users as $user) {
echo $user->posts->count();
}
Nếu có 100 user:
- 1 query lấy users
- 100 query lấy posts
Tổng: 101 query.
Trong production, đây là sát thủ performance.
Tài liệu chính thức:
https://laravel.com/docs/eloquent-relationships
1️⃣ Cách phát hiện N+1
Trong môi trường dev bạn có thể dùng:
- Laravel Debugbar
- Laravel Telescope
- DB::listen()
Ví dụ log query:
DB::listen(function ($query) {
logger($query->sql);
});
Nếu thấy query lặp lại nhiều lần → có thể bạn đang gặp N+1.
2️⃣ Giải pháp: Eager Loading
Thay vì lazy loading:
$users = User::all();
Dùng eager loading:
$users = User::with('posts')->get();
Giờ Laravel sẽ:
- Query users
- Query posts bằng WHERE IN
Chỉ còn 2 query thay vì 101.
3️⃣ Eager Loading Có Điều Kiện
Bạn có thể thêm điều kiện:
$users = User::with(['posts' => function ($query) {
$query->where('published', true);
}])->get();
Giúp giảm data không cần thiết.
4️⃣ Chỉ Select Cột Cần Thiết
Đừng bao giờ load tất cả cột nếu không cần.
Sai:
User::with('posts')->get();
Tốt hơn:
User::select('id', 'name')
->with(['posts:id,user_id,title'])
->get();
Giảm memory usage và tăng tốc đáng kể.
5️⃣ withCount() Thay Vì Load Toàn Bộ Relationship
Nếu bạn chỉ cần số lượng:
Sai:
$user->posts->count();
Đúng:
User::withCount('posts')->get();
Laravel sẽ generate:
SELECT users.*,
(SELECT COUNT(*) FROM posts WHERE posts.user_id = users.id) AS posts_count
FROM users;
Nhanh hơn rất nhiều.
6️⃣ Chunking Khi Làm Việc Với Dữ Liệu Lớn
Nếu bạn xử lý hàng chục nghìn record:
Sai:
User::all();
Đúng:
User::chunk(100, function ($users) {
foreach ($users as $user) {
// xử lý
}
});
Giúp:
- Tránh tràn memory
- Giữ performance ổn định
7️⃣ Index Database – Yếu Tố Bắt Buộc
Eloquent không tự tạo index cho bạn.
Trong migration:
$table->index('user_id');
Không có index:
- Query WHERE chậm
- JOIN chậm
- Full table scan
Đọc thêm về indexing:
https://use-the-index-luke.com/
8️⃣ Phân tích Query Với EXPLAIN
Nếu query chậm, hãy chạy:
EXPLAIN SELECT ...
Bạn cần quan tâm:
- type
- key
- rows
Nếu thấy:
type = ALL
→ đang full table scan.
9️⃣ Khi nào nên dùng Query Builder hoặc Raw SQL?
Eloquent tiện lợi, nhưng không phải lúc nào cũng tối ưu.
Nên dùng raw query khi:
- Report phức tạp
- Join nhiều bảng
- Aggregate lớn
Ví dụ:
DB::select("
SELECT users.id, COUNT(posts.id) as total
FROM users
JOIN posts ON posts.user_id = users.id
GROUP BY users.id
");
Performance cao hơn so với chain nhiều relationship.
Kết luận
Để tối ưu Eloquent trong production:
- Luôn kiểm tra N+1
- Dùng eager loading
- Select đúng cột cần thiết
- Dùng withCount khi chỉ cần số lượng
- Thêm index database
- Phân tích query với EXPLAIN
Eloquent rất mạnh, nhưng nếu dùng sai cách, nó sẽ làm app của bạn chậm một cách âm thầm.
