Nếu anh em thấy hay thì ủng hộ tôi 1 follow + 1 upvote + 1 bookmark + 1 comment cho bài viết này tại Mayfest 2025 nhé. Còn nếu bài viết chưa hữu ích thì tôi cũng hi vọng anh em để lại những góp ý thẳng thắn để tôi có thể học hỏi và cải thiện kiến thức của mình. Cảm ơn anh em nhiều!
Mục lục
-
1. Index trong database là gì?
-
2. Một ví dụ trực quan
-
3. Tại sao Index cực kỳ quan trọng đối với hiệu năng truy vấn?
-
4. Sai lầm phổ biến khi sử dụng Index
1. Index trong database là gì?
Trong database, Index là một cấu trúc dữ liệu được sử dụng để tăng tốc độ truy xuất dòng dữ liệu trong bảng theo một hoặc một số cột cụ thể (là cột đã được đánh index).
Hầu hết các loại index trong MySQL (ví dụ như PRIMARY KEY
, UNIQUE
, INDEX
, FULLTEXT
) sử dụng cấu trúc dữ liệu B-Tree để lưu trữ dữ liệu. Ngoài ra còn có SPATIAL
sử dụng cấu trúc dữ liệu R-Tree, MEMORY
sử dụng hash index. Tôi sẽ phân tích chi tiết các loại này ở các bài viết tiếp theo.
Theo tài liệu chính thức của MySQL:
"Indexes are used to find rows with specific column values quickly. Without an index, MySQL must begin with the first row and then read through the entire table to find the relevant rows." — MySQL Indexes Documentation
Tức là về bản chất, khi không có index, MySQL phải duyệt toàn bộ bảng (Full Table Scan) để tìm dòng dữ liệu phù hợp - một thao tác sẽ gây ra hiệu năng tệ khi dữ liệu lớn.
2. Một ví dụ trực quan
- Để cho các bạn mới học dễ hình dung, tôi sẽ sử dụng hình ảnh về phần Mục lục mà mọi người thường nhìn thấy trong các cuốn sách. Thử tưởng tượng khi bạn đang đọc một cuốn sách về database dày 1000 trang. Và bạn đang muốn tìm nhanh 1 chương trong cuốn sách, đó là chương "Tối ưu truy vấn SQL"
- Nếu cuốn sách mà không có phần Mục lục, bạn buộc phải lật từng trang - hành động này giống như bạn đang duyệt toàn bộ dữ liệu trong bảng (Full Table Scan).
- Nếu có Mục lục, bạn sẽ nhanh chóng tra ngay được chương cần tìm và nhảy đến đúng trang bắt đầu chương đó - hành động này tương tự như khi ta sử dụng Index Lookup.
Thành phần/Hành động | Trong sách | Trong MySQL |
---|---|---|
Chương | Dữ liệu thực tế trong sách | Hàng trong bảng (rows) |
Lật từng trang | Tìm kiếm tuần tự | Full Table Scan |
Mục lục | Danh sách các chương và số trang tương ứng | Index |
Nhảy đến số trang ghi ở Mục lục | Tìm kiếm theo index | Index Lookup |
3. Tại sao Index cực kỳ quan trọng đối với hiệu năng truy vấn?
Một truy vấn không index có thể chậm gấp hàng trăm đến hàng nghìn lần so với truy vấn có index
LƯU Ý:
Ở đây tôi dùng từ "có thể" chứ không phải "chắn chắn" hay "luôn luôn". Vì nó còn phụ thuộc vào nhiều yếu tố như độ lớn dữ liệu, chiến lược đánh index, ..., chứ không phải cứ đánh index bừa là auto nhanh đâu nhé.
Ví dụ nếu bảng của bạn trong DB có dữ liệu ít, thì việc đánh index cũng không mang lại sự khác biệt gì lớn lao cả.
Hoặc kể cả khi bạn có 1 bảng dữ liệu lớn, nhưng câu truy vấn của bạn BẮT BUỘC phải truy cập hầu hết các rows mới tính ra được kết quả đúng, thì thêm index cũng không khiến truy vấn trở nên nhanh hơn đâu nhé.
Ví dụ minh họa:
Giả sử bạn có bảng users
chứa 1 triệu bản ghi, câu truy vấn dưới đây dùng để lấy ra những user có tên là 'Tờ Mờ Sáng học Lập trình':
SELECT * FROM users WHERE name = 'Tờ Mờ Sáng học Lập trình';
- Nếu cột
name
không được đánh index, MySQL phải duyệt toàn bộ 1 triệu dòng (Full Table Scan) . - Nếu cột
name
có được đánh index, MySQL tìm được kết quả chỉ saulog₂(N)
bước. Tức là vớiN = 1 triệu dòng
, thì sẽ tìm được kết quả sau khoảnglog₂(1000000) = 20 bước
.
Tối ưu hóa index đúng cách có thể giảm thời gian truy vấn từ vài giây (s) xuống còn vài mili giây (ms)
4. Sai lầm phổ biến khi sử dụng Index
Sai lầm 1: Đánh index quá nhiều
- Mỗi index tạo thêm chi phí ghi dữ liệu.
INSERT
/UPDATE
sẽ chậm hơn vì tốn thêm thời gian để cập nhật index (nhưng không phải mọi trường hợp)
LƯU Ý:
Tại sao ở gạch đầu dòng thứ 2 tôi lại mở ngoặc là "không phải mọi trường hợp"?
Bởi vì nếu anh em thử đánh index cho cột dữ liệu ở trong mệnh đề
WHERE
của câu lệnhUPDATE... WHERE ...
, thì index sẽ có thể tăng tốc độ thực hiện câu lệnh này nhiều lần.Vì khi đó index giúp tăng tốc phần lọc dữ liệu trong mệnh đề
WHERE
, trong khi thời gian để cập nhật index ấy lại chẳng đáng bao nhiêu.
Sai lầm 2: Không đánh index cho các cột dùng trong WHERE
, JOIN
, ORDER BY
Đây là các nơi cần đánh index nhất.
❌ NHƯNG, một lần nữa tôi phải nhắc với anh em rằng:
Index không phải là chìa khóa vạn năng, nếu cứ áp dụng máy móc, thấy
WHERE
,JOIN
,ORDER BY
cột nào là đánh index cột đó thì cẩn thận có khi còn "vỡ mồm" đó!!!
Ví dụ minh họa:
Giả sử bạn có bảng:
CREATE TABLE users ( id INT PRIMARY KEY, name VARCHAR(100), gender CHAR(1), -- chỉ có 2 giá trị là: 'M' (đại diện cho Male, tức là Nam) và 'F' (đại diện cho Female, tức là Nữ) created_at DATETIME
);
Và bạn tạo index cho cột gender
như sau:
CREATE INDEX idx_gender ON users(gender);
Truy vấn sau đây tưởng chừng như index sẽ giúp nhanh hơn:
SELECT * FROM users WHERE gender = 'M';
❌ NHƯNG:
- Nếu bảng có 1 triệu bản ghi, và 500.000 bản ghi có
gender = 'M'
, thì thực tế index không giúp tăng tốc đáng kể, vì mỗi lần query vẫn trả về quá nhiều rows. - Trong khi đó, index
idx_gender
tốn dung lượng lưu trữ và chi phí cập nhật mỗi lần có thao tácINSERT
/UPDATE
.
Ở đây tôi sẽ liệt kê cho anh em một số trường hợp ngoại lệ mà chúng ta nên cân nhắc KHÔNG ĐÁNH INDEX cho cột dùng trong WHERE
, JOIN
, ORDER BY
:
-
TH1: Cột có tính phân bố thấp (low cardinality), ví dụ như những cột:
Giới tính (Nam/Nữ)
, cột lưuTrạng thái (0/1)
. -
TH2: Cột thường xuyên thay đổi giá trị => chi phí cập nhật index cao. Ví dụ: cột
last_login
được cập nhật mỗi lần user login. Nếu đánh index cho cột này thì mỗi lần update sẽ phải maintain index => làm chậm tốc độ ghi. Nếu queries theolast_login
không thật sự thường xuyên hoặc không selective thì bỏ index sẽ giúp tốc độ ghi tốt hơn. -
TH3: Dữ liệu trong bảng quá nhỏ ví dụ chỉ loanh quanh 200 - 300 bản ghi, hoặc truy vấn hiếm khi dùng đến => chi phí tạo và duy trì index không đáng.
-
TH4: Mệnh đề
WHERE
sử dụngLIKE
vớileading wildcard. Ví dụWHERE name LIKE '%Sang'
=> MySQL không thể tận dụng index nếu pattern bắt đầu bằng%
=> Full Table Scan vẫn xảy ra. -
TH5: Column với giá trị NULL quá nhiều. Ví dụ: cột bị
NULL
với > 90% bản ghi => các câu truy vấn kiểuWHERE cot IS NOT NULL
mặc dù có dùng index nhưng thực tế nó vẫn phải scan hầu hết bảng, index ít có giúp ích gì ở đây.
Tôi cũng thường sử dụng câu lệnh EXPLAIN
để kiểm tra kế hoạch thực thi trước khi đưa ra quyết định. Tôi sẽ có một bài viết khác để phân tích rõ hơn về câu lệnh này.
Sai lầm 3: Sử dụng hàm (function) trong mệnh đề WHERE trên cột đã đánh index, làm mất tác dụng của index
Ví dụ ta có bảng users
, được đánh index ở cột created_at
:
CREATE INDEX idx_created_at ON users(created_at);
❌ Nhưng khi ta chạy câu lệnh truy vấn để lọc ra những user được tạo vào năm 2024:
SELECT * FROM users WHERE YEAR(created_at) = 2025;
thì khi chạy lệnh EXPLAIN
ta sẽ thấy câu lệnh trên sẽ vô hiệu hóa index (quát sát cột type = ALL
và cột key = NULL
), vì MySQL không thể sử dụng index khi cột bị bao bởi một hàm. Khi ấy MySQL sẽ buộc phải thực hiện full table scan.
✅ Thay vào đó, chúng ta nên viết lại câu lệnh theo cách sau:
SELECT * FROM users WHERE created_at BETWEEN '2025-01-01' AND '2025-12-31';
Lúc này index mới phát huy hiệu quả, chạy lại câu lệnh EXPLAIN
để kiểm chứng:
Sai lầm 4: Hiểu sai/Không hiểu về cách hoạt động của Composite Index
Composite index là một index bao gồm nhiều hơn một cột. Nó được sử dụng để tăng tốc độ truy vấn khi truy vấn đó có điều kiện lọc, sắp xếp hoặc nhóm theo nhiều cột cùng lúc.
Thế nhưng tôi đã gặp nhiều bạn hiểu sai hoặc không hiểu về cách hoạt động của composite index, các bạn ấy tạo một composite index (cột1, cột2)
, nhưng đến khi viết câu lệnh truy vấn thì lại chỉ WHERE
theo cột2
, thì composite index khi ấy thực tế là không được sử dụng.
Ví dụ:
Tôi có bảng users
được đánh composite index:
CREATE INDEX idx_name ON users (last_name, first_name);
-
✅ TH1: Truy vấn đúng cách (sử dụng tốt composite index)
SELECT * FROM users WHERE last_name = 'Tran' AND first_name = 'Sang';
hoặc
SELECT * FROM users WHERE first_name = 'Sang' AND last_name = 'Tran';
Khi chạy lệnh
EXPLAIN
ta sẽ thấy truy vấn sử dụng cả hai cột theo đúng thứ tự trong composite indexidx_name
(quan sát cộttype = ref
và cộtkey = idx_name
): -
❌ TH2: Truy vấn sử dụng sai thứ tự cột trong composite index
SELECT * FROM users WHERE first_name = 'Sang';
Kết quả khi chạy lệnh
EXPLAIN
ta sẽ thấy MySQL thực hiện quét toàn bộ bảng - full table scan (quát sát cộttype = ALL
và cộtkey = NULL
):Lý do là vì composite index là
(last_name, first_name)
=> index này sẽ không dùng được nếu không cólast_name
trong mệnh đềWHERE
-
⚠️ TH3: Truy vấn chỉ dùng cột đầu tiên trong composite index
SELECT * FROM users WHERE last_name = 'Tran';
Kết quả là composite index vẫn được dùng, mặc dù không tối ưu bằng việc dùng đầy đủ cả hai cột (quan sát cột
type = ref
và cộtkey = idx_name
)
Vậy tóm lại, khi composite index để thứ tự là (last_name, first_name)
thì:
Truy vấn lọc theo | Index có được dùng hay không? |
---|---|
last_name |
✅ Có |
last_name, first_name |
✅ Tốt nhất |
first_name, last_name |
✅ Giống last_name, first_name |
first_name |
❌ Không |
Đây là nguyên tắc leftmost prefix trong MySQL:
"MySQL cannot use the index to perform lookups if the columns do not form a leftmost prefix of the index." — 10.3.6 Multiple-Column Indexes
Trong bài tiếp theo, tôi sẽ cùng anh em đào sâu vào cấu trúc dữ liệu dùng để lưu trữ Index trong MySQL
Hẹn gặp lại 👋
🙋🏻♂️ Một số kênh mạng xã hội khác mà tôi dùng để chia sẻ và trao đổi với anh em kiến thức về ngành CNTT và lập trình:
-
Group "Khi nào giỏi lập trình thì đổi tên 🫢": https://www.facebook.com/groups/gioilaptrinhthidoiten
-
Page "CLB Lập trình - THPT Ngọc Tảo": https://www.facebook.com/clb.it.ngoctao/
-
TikTok "CLB Lập trình - THPT Ngọc Tảo": https://www.tiktok.com/@clb.it.ngoctao/
-
Youtube "Tờ Mờ Sáng học Lập trình": https://www.youtube.com/@tmsanghoclaptrinh/