🧠 Mở đầu
Thời gian đầu mới đi làm, công ty có dự án liên quan đến Nông nghiệp, làm cho khách hàng Nhật. Phía Nhật lo phần thiết kế database và tài liệu, còn team ở Việt Nam chịu trách nhiệm coding và testing.
Mọi thứ diễn ra suôn sẻ. Code xong chức năng, test ok, client duyệt, mọi người đều vui.
Cho đến khi... dữ liệu bắt đầu nhiều lên.
Ứng dụng bỗng trở nên chậm chạp. Tôi nghi ngờ ngay JavaScript, vì lúc đó dùng Ajax
để gọi backend và hiển thị dữ liệu. Nghĩ rằng dữ liệu lớn làm javascript render chậm. Nhưng khi debug sâu bên trong vấn đề nằm ở cách viết câu SQL và thiết kế index trong database
❓ Những câu hỏi lúc đó...
- Vì sao
LIKE '%abc'
không dùng được index? - Vì sao khi dùng hàm trong
WHERE
, index lại không hoạt động? - Vì sao đã tạo index
index_AB(column_A, column_B)
mà tìm theocolumn_B='abc'
vẫn không tận dụng được index?
Câu trả lời cho tất cả các câu hỏi đó nằm trong cấu trúc của B-Tree.
Hãy tưởng tượng B-Tree giống như một thư viện được sắp xếp gọn gàng. Thay vì phải lục tung từng cuốn sách, bạn có thể tra cứu nhanh nhờ cấu trúc được phân tầng – chính xác và hiệu quả.
🌳 Cấu trúc cơ bản của B-Tree
🔹 Index với một column
Khi bạn tạo index trên một column duy nhất, cấu trúc B-Tree sẽ trông như sau:
(Hình ảnh giản luợc)
❓ Vì sao LIKE '%abc'
không dùng index?
👉 Vì B-Tree được xây dựng để duyệt từ ký tự đầu tiên. Khi bắt đầu bằng %
, bạn đã mất gốc – không xác định được điểm khởi đầu trong cây, khiến việc tìm kiếm phải duyệt toàn bộ dữ liệu. Index lúc này bị vô hiệu.
❓ Vì sao khi dùng hàm (LOWER(column_name)
, LTRIM(column_name)
...) trong điều kiện thì index không chạy?
👉 Vì B-Tree chỉ hoạt động dựa trên giá trị gốc của column. Khi bạn áp dụng hàm, cơ sở dữ liệu phải tính toán lại từng dòng, và các giá trị sau xử lý không còn khớp với cây đã xây dựng. Kết quả: Index không được tận dụng.
🔹 Index với nhiều column
Giả sử bạn tạo index: index_AB(column_A, column_B)
, thì cấu trúc B-Tree sẽ ưu tiên column đầu tiên để xây dựng.
Ví dụ với index_ho_ten(ho, ten)
, cây sẽ được sắp xếp theo thứ tự của ho
trước, sau đó mới đến ten
.
(Hình giản lược, mô phỏng cấu trúc B-Tree)
❓ Vì sao WHERE column_B = 'abc'
không dùng được index?
👉 Vì cây B-Tree khởi động từ column_A
. Nếu bạn không lọc theo column_A
, thì không thể định hướng để duyệt đến column_B
. Cơ bản là không có đường vào cây.
❓ Vậy cột nào nên là column đầu tiên trong index?
👉 Là cột có tính phân biệt cao (distinctive) – tức giá trị ít bị trùng lặp. Điều này giúp cây phân nhánh tốt hơn, giảm đáng kể số dòng cần duyệt khi truy vấn.
⚠️ Ghi chú thêm với Column is_deleted
Nhiều hệ thống có sử dụng xóa luận lý (logical delete), nghĩa là không xóa thật dữ liệu mà chỉ cập nhật is_deleted = 1
.
Dù is_deleted
có tính phân biệt thấp (đa số giá trị là 0), nhưng nếu bạn thường xuyên truy vấn kiểu:
WHERE is_deleted = 0 AND name LIKE 'A%'
thì việc tạo index như index_del_name(is_deleted, name)
lại giúp truy vấn nhanh hơn: lọc nhanh is_deleted = 0
trước, sau đó tìm tiếp theo name
💡 Tuy nhiên, đây không phải quy tắc cố định. Nếu truy vấn của bạn không lọc is_deleted
, mà chỉ tìm theo name
, thì nên đặt is_deleted
sau trong thứ tự index.
(Tùy bài toán cụ thể ta sẽ đặt is_deleted
ở vị trí phù hợp, sau khi tạo index, ta phải thử lại, xem query plan để phán đoán tính hiệu quả của nó như thế nào)
✅ Tổng kết
Khi đã nắm được cơ bản cách B-Tree xây dựng và duyệt, bạn sẽ biết được:
- Vì sao một số câu SQL không thể dùng index.
- Cách lựa chọn column đầu tiên trong index ảnh hưởng ra sao đến hiệu năng.
- Index rất quan trọng trong bài toán truy xuất dữ liệu từ database, nếu không thiết kế không tốt hệ thống sẽ chậm khi dữ liệu lớn dần
✌️ Cảm ơn bạn đã đọc đến đây. Nếu thấy hữu ích, hãy chia sẻ cho bạn bè đang vật lộn với Index nhé!