Gần đây khi rà soát một codebase Laravel, mình bắt gặp lỗi N + 1 query khá phổ biến. N + 1 query âm thầm làm chậm ứng dụng: những tác vụ lẽ ra chỉ chạy 1 lần lại bị lặp lại 4–5 lần (hoặc hơn).
Dưới đây là hai kịch bản N + 1 thường gặp và cách khắc phục.
Kịch bản 1: Vòng lặp tạo giao dịch trong cơ sở dữ liệu
Lỗi thường gặp:
foreach ($request->transactions as $transaction) { Transaction::create([ "transactionId" => $transaction->id, "amount" => $transaction->amount // and the rest of the code ]);
}
Mỗi lần vòng foreach
chạy sẽ tạo một bản ghi mới ⇒ N + 1 query, làm chậm tuyến (route) gọi hàm này.
Cách khắc phục:
- Khai báo một mảng rỗng trước vòng
foreach
. - Trong vòng
foreach
, chuẩn bị dữ liệu cần chèn. - Sau vòng
foreach
, insert một lần duy nhất.
Triển khai thay đổi:
- Đầu tiên, khai báo một mảng rỗng trước dòng vòng lặp foreach
$storeTransaction = [];
- Lặp lại mã và chuẩn bị cách bạn sẽ chèn nó
$now = now(); foreach ($request->transactions as $transaction) { $storeTransaction[] = [ "transactionId" => $transaction->id, "amount" => $transaction->amount, "created_at" => $now(), "updated_at" => $now(), ];
}
- Chèn tất cả vào cơ sở dữ liệu cùng một lúc
Transaction::insert($storeTransaction);
Như vậy, thay vì 10–15 lệnh insert, bạn chỉ gửi một câu lệnh tới DB.
Kịch bản 2: Lấy bản ghi rồi cập nhật nếu tồn tại
Lỗi thường gặp:
public function updateTransactionStatus($id)
{ $transaction = Transactions::where('id', $id)->get(); if (!$transaction) { return $this->errorResponseHelper('fail', 'Transaction not found', 404); } Transactions::where('id', $id)->update(['status' => 1]); return $this->successResponseHelper('success', 'Transaction updated successfully', $transaction);
}
Hàm trên hoạt động nhưng không tối ưu: truy vấn DB hai lần rồi mới cập nhật.
Cách khắc phục:
- Truy vấn lấy bản ghi trước.
- Nếu không có, trả lỗi.
- Nếu có, dùng luôn đối tượng vừa lấy để cập nhật.
public function updateTransactionStatus($id)
{ $transaction = Transactions::where('id', $id)->get(); if (!$transaction) { return $this->errorResponseHelper('fail', 'Transaction not found', 404); } $transaction->update(['status' => 1]); return $this->successResponseHelper('success', 'Transaction updated successfully', $transaction);
}
Bạn đã “bắn một mũi tên trúng hai (thậm chí ba) đích”: giảm số lần truy vấn và cập nhật ngay trên bản ghi đã có.
Nếu codebase của bạn có những đoạn tương tự, hãy tối ưu ngay.
Hy vọng bài viết này hữu ích đối với các bạn!