MySQL chậm không phải lúc nào cũng do server yếu. Rất nhiều trường hợp vấn đề nằm ở cách thiết kế query, cách lấy dữ liệu và việc chưa dùng index đúng chỗ.
Một query chỉ cần chạy qua vài chục dòng dữ liệu sẽ rất khác với query phải scan vài trăm nghìn dòng rồi mới lọc ra kết quả cần thiết.
Kết luận nhanh
Nếu cần nhớ ngắn gọn, có 4 ý quan trọng:
indexgiúp MySQL tìm đúng vùng dữ liệu nhanh hơn thay vì quét cả bảng- query không có index phù hợp rất dễ rơi vào
full table scan SELECT *hoặc lấy quá nhiều row rồi mới lọc ở code sẽ làm chậm cả database lẫn app- tối ưu MySQL không chỉ là thêm index, mà còn là lấy đúng số cột và đúng số dòng cần dùng
Index trong MySQL có vai trò gì?
Bạn có thể hình dung index giống như mục lục trong sách.
- không có mục lục: phải lật từng trang để tìm nội dung
- có mục lục: nhảy gần như ngay tới phần cần đọc
Trong MySQL cũng vậy:
- không có index phù hợp, database phải scan rất nhiều row
- có index phù hợp, database xác định tập dữ liệu cần đọc nhanh hơn rất nhiều
Index đặc biệt quan trọng với các cột thường dùng trong:
WHEREJOINORDER BYGROUP BY
Ví dụ bảng users:
SELECT id, name, email
FROM users
WHERE email = '[email protected]';
Nếu email chưa có index, MySQL có thể phải duyệt rất nhiều row để tìm đúng user.
Nếu email có index, nó tìm nhanh hơn rất nhiều vì không cần quét cả bảng.
Khi nào thiếu index làm MySQL chậm rõ nhất?
Thiếu index thường gây chậm mạnh khi:
- bảng đã lớn
- query chạy thường xuyên
- query có filter rõ ràng nhưng MySQL vẫn phải scan nhiều row
- có sắp xếp hoặc join trên cột chưa được index phù hợp
Ví dụ:
SELECT id, user_id, status, created_at
FROM orders
WHERE status = 'paid'
ORDER BY created_at DESC
LIMIT 20;
Nếu không có index phù hợp, MySQL có thể:
- scan rất nhiều row để tìm
status = 'paid' - sau đó còn phải sort lại theo
created_at
Một index phù hợp hơn có thể là:
CREATE INDEX idx_orders_status_created_at
ON orders (status, created_at);
Điểm quan trọng là không phải cứ có index là đủ.
Thứ tự cột trong composite index rất quan trọng.
Query chậm vì lấy quá nhiều cột không cần thiết
Đây là lỗi rất hay gặp:
SELECT *
FROM users
WHERE status = 'active';
Vấn đề của SELECT *:
- đọc nhiều dữ liệu hơn mức cần thiết
- tốn băng thông giữa DB và app
- tăng memory usage ở app
- dễ kéo theo đọc cả các cột text lớn hoặc JSON không dùng tới
Nếu màn hình chỉ cần id, name, email, thì nên viết rõ:
SELECT id, name, email
FROM users
WHERE status = 'active';
Với bảng lớn hoặc traffic cao, việc chỉ lấy đúng cột cần dùng tạo ra khác biệt rất rõ.
Query chậm vì lấy dư dữ liệu rồi mới lọc ở code
Đây là kiểu tối ưu sai chỗ rất phổ biến.
Ví dụ:
SELECT id, user_id, status, total, created_at
FROM orders
WHERE created_at >= '2026-06-01';
Sau đó ở backend mới làm:
const paidOrders = orders.filter((item) => item.status === 'paid').slice(0, 20);
Vấn đề:
- DB trả về nhiều row hơn cần thiết
- app phải tốn RAM để giữ data
- app phải tốn CPU để filter lại
- network giữa app và DB cũng bị tốn vô ích
Trường hợp này nên đẩy việc lọc về cho MySQL:
SELECT id, user_id, total, created_at
FROM orders
WHERE created_at >= '2026-06-01'
AND status = 'paid'
ORDER BY created_at DESC
LIMIT 20;
Nguyên tắc thực dụng là:
- lọc ở database
- giới hạn số row ở database
- chỉ lấy đúng cột cần thiết
Đừng lấy 5000 row rồi mới cắt còn 20 row trong code nếu query hoàn toàn có thể làm điều đó ngay từ đầu.
Query chậm vì có index nhưng vẫn dùng sai cách
Có index chưa chắc query đã chạy nhanh nếu cách viết làm MySQL khó tận dụng index.
Một số ví dụ hay gặp:
Dùng hàm lên cột đang filter
SELECT id, created_at
FROM orders
WHERE DATE(created_at) = '2026-06-16';
Kiểu viết này có thể làm MySQL khó dùng index trên created_at.
Nên đổi thành:
SELECT id, created_at
FROM orders
WHERE created_at >= '2026-06-16 00:00:00'
AND created_at < '2026-06-17 00:00:00';
Dùng LIKE với wildcard ở đầu
SELECT id, name
FROM products
WHERE name LIKE '%iphone%';
Query này thường không tận dụng index kiểu BTREE như bạn mong đợi.
Nếu use case là search text thật sự, có thể cần:
- full-text index
- search engine riêng
- hoặc đổi lại logic tìm kiếm
Join trên cột chưa có index
SELECT o.id, u.name
FROM orders o
JOIN users u ON o.user_id = u.id
WHERE o.status = 'paid';
Nếu orders.user_id không có index phù hợp, join có thể chậm rõ rệt khi data lớn.
Index nào cũng tạo bừa có được không?
Không.
Index giúp đọc nhanh hơn, nhưng cũng có chi phí:
- tốn thêm dung lượng lưu trữ
- insert/update/delete có thể chậm hơn vì phải cập nhật index
- quá nhiều index làm optimizer khó chọn hơn trong một số case
Vì vậy không nên nghĩ theo kiểu:
- query chậm -> thêm index bừa
Nên nghĩ theo kiểu:
- query nào chạy nhiều
- cột nào đang filter/join/order thực sự
- index hiện tại đã đủ chưa
- thêm index này có phục vụ đúng query quan trọng không
Nên kiểm tra query chậm bằng gì?
Ít nhất nên dùng:
EXPLAIN- slow query log
- nhìn lại chính câu query đang lấy bao nhiêu cột, bao nhiêu row
Ví dụ:
EXPLAIN
SELECT id, user_id, total
FROM orders
WHERE status = 'paid'
ORDER BY created_at DESC
LIMIT 20;
Nếu thấy dấu hiệu như:
type: ALL- scan rows quá lớn
- không dùng index mong đợi
thì đó là tín hiệu nên xem lại index hoặc cách viết query.
Những case rất hay làm MySQL chậm trong project thật
Đây là các lỗi gặp rất nhiều trong backend thực tế:
- filter bằng cột chưa có index
- join bằng cột chưa có index
SELECT *trong khi chỉ cần vài cột- lấy hàng nghìn row rồi filter/sort/paginate ở code
- dùng hàm như
DATE(),LOWER()trực tiếp trên cột filter ORDER BYtrên cột lớn nhưng không có index phù hợp- phân trang sâu bằng
OFFSETquá lớn
Trong số đó, hai lỗi dễ gặp nhất và tốn tài nguyên nhiều nhất thường là:
- chưa đánh index đúng chỗ
- lấy dữ liệu thừa rồi xử lý tiếp ở tầng code
Khi nào nên sửa query trước, khi nào nên thêm index trước?
Nếu query đang lấy quá nhiều data không cần thiết, nên sửa query trước.
Ví dụ:
- bỏ
SELECT * - thêm
LIMIT - đẩy filter xuống SQL
- bỏ logic lọc ở app nếu DB làm được
Nếu query đã viết hợp lý nhưng vẫn scan quá nhiều row, lúc đó mới xem tiếp:
- có thiếu index không
- thứ tự cột trong index có đúng không
- join/order/filter đã khớp với index chưa
Một checklist ngắn để tránh làm MySQL chậm
Trước khi chốt một query quan trọng, nên tự check:
- Query này có đang dùng
SELECT *không? - Có đang lấy nhiều row hơn thực tế cần dùng không?
- Có đang filter, sort, paginate ở code thay vì ở SQL không?
- Cột trong
WHERE,JOIN,ORDER BYđã có index phù hợp chưa? - Có dùng hàm lên cột khiến index khó hoạt động không?
- Đã chạy
EXPLAINchưa?
Kết luận
Vai trò lớn nhất của index là giúp MySQL giảm lượng dữ liệu phải quét để tìm ra kết quả cần thiết. Nhưng chỉ thêm index thôi chưa đủ.
Nhiều hệ thống chậm vì những lỗi rất cơ bản:
- chưa đánh index
- query lấy quá nhiều cột
- lấy quá nhiều row
- lấy dư dữ liệu rồi mới xử lý ở code
Nếu muốn tối ưu hiệu năng MySQL thực tế, hãy bắt đầu từ 3 câu hỏi:
- query này có đang lấy đúng dữ liệu cần dùng không?
- database có đang phải quét quá nhiều row không?
- index hiện tại có thực sự phục vụ câu query này không?
Làm đúng 3 điểm này thường đã giúp phần lớn query nhanh lên rõ rệt mà chưa cần đụng tới hạ tầng.