Đừng Chỉ Đo Thời Gian, Hãy Đo "Nỗ Lực" Của Database: Nghệ Thuật Tối Ưu Query
Mọi Backend Developer đều từng ít nhất một lần thốt lên: "Ơ, máy em chạy nhanh lắm mà lên Prod nó lag thế nhỉ?". Câu trả lời thường nằm ở việc chúng ta đang đo tốc độ Query theo cách "cưỡi ngựa xem hoa".
Thời gian thực thi (Execution Time) chỉ là phần nổi của tảng băng. Để thực sự làm chủ hiệu năng, bạn cần biết Database đã phải "vất vả" như thế nào để trả về kết quả đó. Bài viết này sẽ hướng dẫn bạn cách "khám bệnh" Query một cách chuyên sâu nhất.
1. Tại sao đo thời gian (Time) là chưa đủ?
Việc đo bằng microtime(true) hay Stopwatch ở phía code Application có một nhược điểm chí mạng: Độ nhiễu.
Tốc độ trả về bị ảnh hưởng bởi:
- Độ trễ mạng (Network Latency).
- Tải của CPU tại thời điểm đó.
- Cơ chế Cache của Database (Query chạy lần 2 luôn nhanh hơn lần 1 nhờ Buffer Pool).
Chìa khóa: Thay vì chỉ đo "Giây", hãy đo "Resource Usage" (Số hàng đã quét, số lần đọc đĩa, bộ nhớ sử dụng).
2. Tầng Database: Đọc hiểu "Bản đồ thực thi"
2.1. Đừng chỉ EXPLAIN, hãy dùng EXPLAIN ANALYZE
Kể từ MySQL 8.0, chúng ta có một vũ khí cực mạnh là EXPLAIN ANALYZE. Nó không chỉ "dự đoán" mà thực sự chạy query và báo cáo con số thực tế.
EXPLAIN ANALYZE
SELECT * FROM orders WHERE customer_id = 123 AND status = 'completed';
Bạn cần nhìn vào đâu?
Actual time: Thời gian thực tế để lấy được dòng dữ liệu đầu tiên và toàn bộ dữ liệu.
Rows read: Database đã phải duyệt bao nhiêu dòng để lấy ra kết quả? Nếu bạn cần 10 dòng mà nó quét 1 triệu dòng -> Missing Index chắc chắn!
2.2. MySQL Performance Schema
Để soi sâu hơn vào từng giai đoạn (Parsing, Statistics, Planning, Execution), hãy dùng:
SET profiling = 1;
-- Chạy query của bạn ở đây
SHOW PROFILE FOR QUERY 1;
Bạn sẽ thấy chính xác "vàng" của bạn rơi ở đâu: Do Sending data hay System lock?
3. Tầng Application: Debug "như một quý ông"
Nếu bạn dùng Laravel, đừng chỉ Log::info(microtime()). Hãy tận dụng sức mạnh của Query Logging kết hợp với thông tin ngữ nghĩa.
3.1. Sử dụng Listeners để bắt "Query chậm"
Thay vì rải code khắp nơi, hãy đăng ký trong AppServiceProvider:
DB::listen(function ($query) {
if ($query->time > 500) { // Nếu query chạy lâu hơn 500ms
Log::warning("Query chậm phát hiện!", [
'sql' => $query->sql,
'bindings' => $query->bindings,
'time' => $query->time,
'url' => request()->fullUrl()
]);
}
});
3.2. Chặn đứng N+1 Query từ "vòng gửi xe"
N+1 là "sát thủ" thầm lặng nhất. Hãy dùng Model::preventLazyLoading(!app()->isProduction()) để hệ thống tự quăng lỗi ngay khi bạn quên eager loading. Đây mới là cách đo hiệu năng chủ động!
4. Những "Cạm bẫy" khiến phép đo của bạn sai bét
Cold Cache vs Warm Cache: Database thường lưu kết quả vào RAM. Khi bạn đo, hãy thử chạySQL_NO_CACHE(trong MySQL 5.7 trở xuống) để biết tốc độ thực khi không có cache.Data Distribution: Query chạy nhanh với 100 bản ghi ở Local không có nghĩa nó sẽ nhanh với 10 triệu bản ghi ở Prod. Luôn dùng các công cụ Seed dữ liệu lớn để test.Transaction Lock: Đôi khi query chậm không phải do nó dở, mà do nó đang phải đợi một thằng khác "nhả" khóa (Lock) ra.
5. Checklist tối ưu Query cho anh em
[ ] Có Index chưa? (Kiểm tra cột trong WHERE, JOIN, ORDER BY).
[ ] Có lấy thừa dữ liệu không? (Thay SELECT * bằng các cột cần thiết).
[ ] Có dùng sai kiểu dữ liệu không? (So sánh chuỗi với số khiến Index vô dụng).
[ ] Có dùng Function trong WHERE không? (Ví dụ: WHERE YEAR(created_at) = 2026 sẽ làm hỏng Index).
Kết luận
Đo tốc độ Query không đơn giản là bấm đồng hồ. Đó là quá trình thấu hiểu cách Database vận hành. Hãy tập thói quen đọc EXPLAIN mỗi khi viết một câu Query phức tạp. Đó chính là ranh giới giữa một thợ code và một Engineer thực thụ.
Hành động ngay: Hãy cài đặt Sentry hoặc Spatie Ray (như bài trước mình chia sẻ) để có cái nhìn trực quan nhất về Query trong dự án của bạn nhé!
Cảm ơn các bạn đã đọc bài. Nếu thấy hữu ích, hãy tặng mình 1 Upvote để có động lực viết tiếp các bài chuyên sâu về Database nhé!
All Rights Reserved