IDX
SLOW
MySQL Index
scan • query • data

Vai trò của index và những kiểu query làm MySQL chậm

Vì sao index quan trọng trong MySQL, và những kiểu query hay làm chậm như chưa đánh index, SELECT quá nhiều data, hoặc lấy dư dữ liệu rồi mới xử lý ở code.

10 phút đọc16/06/2026

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:

  1. index giúp MySQL tìm đúng vùng dữ liệu nhanh hơn thay vì quét cả bảng
  2. query không có index phù hợp rất dễ rơi vào full table scan
  3. 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
  4. 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:

  • WHERE
  • JOIN
  • ORDER BY
  • GROUP 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ế:

  1. filter bằng cột chưa có index
  2. join bằng cột chưa có index
  3. SELECT * trong khi chỉ cần vài cột
  4. lấy hàng nghìn row rồi filter/sort/paginate ở code
  5. dùng hàm như DATE(), LOWER() trực tiếp trên cột filter
  6. ORDER BY trên cột lớn nhưng không có index phù hợp
  7. phân trang sâu bằng OFFSET quá 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:

  1. Query này có đang dùng SELECT * không?
  2. Có đang lấy nhiều row hơn thực tế cần dùng không?
  3. Có đang filter, sort, paginate ở code thay vì ở SQL không?
  4. Cột trong WHERE, JOIN, ORDER BY đã có index phù hợp chưa?
  5. Có dùng hàm lên cột khiến index khó hoạt động không?
  6. Đã chạy EXPLAIN chư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:

  1. query này có đang lấy đúng dữ liệu cần dùng không?
  2. database có đang phải quét quá nhiều row không?
  3. 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.