
Khi Database có 1 Triệu Rows, Eloquent sẽ thế nào?
Eloquent rất tiện khi project nhỏ.
Nhưng khi table đạt:
- 500k rows
- 1M rows
- 10M rows
Bạn sẽ bắt đầu thấy:
- Pagination chậm dần
- Query filter mất nhiều thời gian
- Memory tăng đột biến
- CPU spike khi traffic cao
Bài này tập trung vào cách tối ưu Eloquent trong môi trường production thực tế.
Tài liệu chính thức:
https://laravel.com/docs/eloquent
1️⃣ Offset Pagination là “kẻ giết hiệu năng”
Mặc định:
User::paginate(20);
Laravel sẽ generate:
SELECT * FROM users LIMIT 20 OFFSET 20000;
Khi OFFSET lớn:
- Database vẫn phải scan qua hàng chục nghìn row
- Query chậm theo cấp số nhân
Giải pháp: Cursor Pagination
User::orderBy('id')->cursorPaginate(20);
Cursor pagination:
- Không dùng OFFSET
- Dùng last seen value
- Nhanh hơn rất nhiều khi page sâu
Docs:
https://laravel.com/docs/pagination#cursor-pagination
2️⃣ Chunk vs Cursor vs Lazy
Chunk
User::chunk(100, function ($users) {
foreach ($users as $user) {
// xử lý
}
});
Phù hợp:
- Batch processing
- Cron job
Cursor
foreach (User::cursor() as $user) {
// xử lý
}
- Dùng generator
- Giữ memory cực thấp
- Phù hợp dataset lớn
Lazy
User::lazy();
Tương tự cursor nhưng linh hoạt hơn trong pipeline.
3️⃣ Composite Index – Thứ Tự Rất Quan Trọng
Sai:
$table->index(['created_at', 'tenant_id']);
Nếu query chính là:
WHERE tenant_id = ? ORDER BY created_at DESC
Thì index nên là:
$table->index(['tenant_id', 'created_at']);
Thứ tự index ảnh hưởng trực tiếp đến query plan.
Đọc thêm:
https://use-the-index-luke.com/
4️⃣ whereDate Làm Mất Index
Sai:
User::whereDate('created_at', now())->get();
Điều này khiến database không dùng index vì có function bọc cột.
Tốt hơn:
User::whereBetween('created_at', [
now()->startOfDay(),
now()->endOfDay()
])->get();
Giữ được index.
5️⃣ EXPLAIN – Công Cụ Không Thể Thiếu
Nếu query chậm, chạy:
EXPLAIN SELECT ...
Quan tâm:
- type (ALL là xấu)
- key (index nào đang dùng)
- rows (bao nhiêu row bị scan)
Nếu thấy:
type = ALL
→ full table scan → cần index.
6️⃣ Bulk Insert & Update
Sai:
foreach ($users as $data) {
User::create($data);
}
Đúng:
User::insert($users);
Bulk insert giảm số lượng query đáng kể.
7️⃣ N+1 Ở Scale Lớn Càng Nguy Hiểm
Ở 100 record → có thể chậm nhẹ.
Ở 10k record → có thể làm server sập.
Luôn dùng:
User::with('posts')->get();
Và bật:
Model::preventLazyLoading();
8️⃣ Cache Chiến Lược
Nếu query:
- Tính toán nhiều
- Ít thay đổi
Dùng:
Cache::remember('dashboard_stats', 300, function () {
return ReportService::generate();
});
Redis giúp giảm load database đáng kể.
Docs cache:
https://laravel.com/docs/cache
9️⃣ Khi Nào Phải Bỏ Eloquent?
Eloquent không tối ưu cho:
- Complex reporting
- Heavy aggregation
- Data analytics
Trong trường hợp đó:
DB::select("SELECT ... GROUP BY ...");
Hoặc dùng read replica riêng cho reporting.
Kết luận
Khi làm việc với dataset lớn:
- Tránh offset pagination
- Dùng cursor hoặc chunk
- Tối ưu index đúng thứ tự
- Tránh function làm mất index
- Phân tích query bằng EXPLAIN
- Cache hợp lý
- Không lạm dụng ORM
