Câu này slow query, hệ thống response chậm quá,... tìm nguyên nhân đi em, cải thiện tốc độ truy vấn này đi... Đó là những gì tôi nhận được văng vẳng bên tai của a PM. Việc đầu tiên là explain câu query đó ra xem như thế nào đã, và giải pháp đầu tiên tôi nghĩ đến là đánh index. Hãy nói mind set của bạn giống tôi đi. Ok, chủ đề hôm nay là Index trong Database, let's go!...
Đặt vấn đề
Giả sử bây giờ tôi có 1 table "ex" chứa thông tin người yêu cũ bao gồm: tên, tuổi, quê quán, sđt, facebook, số đo ba vòng... và bảng này cỡ khoảng triệu records đi =]]] Đến một ngày đẹp trời, tự dưng muốn tìm lại info em Xoan chẳng hạn.
Select * from ex where name = "Xoan"
Lúc này hệ thống phải đọc ra từng row trong bảng "ex", so sánh từ đầu đến cuối, quả là một cách làm tệ và chậm chạp nhất. Vậy làm sao để tăng tốc độ tìm kiếm em Xoan, đi thẳng vào vấn đề đó chính là làm thế nào để giảm được số lượng bản ghi khi truy vấn. Xong, index chắc rồi, vậy....
Index là gì
index là việc cấu trúc dữ liệu, lưu trữ theo một cơ chế chuyên biệt để tìm ra các record một cách nhanh chóng. Ví Index đơn giản như là mục lục của một quyển sách, khi cần tìm một chủ đề, bài viết nào đó thì thay vì phải xem, tìm kiếm toàn bộ quyển sách ta chỉ cần xem chỉ mục tìm đúng bài viết đó rồi từ số trang được đính kèm là ta có thể đến chính xác nội dung bài viết mong muốn.
Mysql hỗ trợ 3 loại index khác nhau cho database: B-tree, Hash, R-tree. Tuy nhiên R-tree chỉ được sử dụng cho các loại dữ liệu không gian spacial data, ví dụ như vị trí nhà hàng hoặc đa giác mà bản đồ điển hình được tạo ra: đường phố, tòa nhà... R-tree thường ít khi gặp phải nên tạm thời mình sẽ bỏ qua, nếu có dịp thì hẹn mọi người tại một bài viết khác.
Hash index
Hash index thì sao, nó được tổ chức dưới dạng key-value với key
là kết quả hash value của column được đánh index và value
sẽ chứa 1 con trỏ đến chính xác row tương ứng. Chính vì vậy, hash index tỏ ra mạnh mẽ khi thực hiện các phép truy vấn với toán tử = hay [IN, NOT IN]. Tuy nhiên lại vô dụng khi gặp các trường truy vấn với điều kiện như >
, ‘quanlx’ thì ngược lại. … tìm kiếm
nhị phân cho đến khi ra kết quả.
TH2: đánh index cho nhiều trường - ví dụ [country, name, email] Vẫn như trên, nhưng có điều, column country
sẽ được chọn lấy làm value chính để sắp xếp index trước, sau đó mới đến name
và email
.
Tức là trong truy vấn, mặc định B-tree index sẽ đi tìm kiếm theo country trước, khi tìm được chính xác country
thì mới tìm kiếm đến name
thuộc country
đó, và cuối cùng là email
. Chính vì vậy, vị
trí column để đánh index là hết sức quan trọng.
Nếu bạn nhảy cóc bỏ qua việc tìm kiếm country
trước để tìm kiếm trực tiếp name thì index sẽ không có tác dụng. Vì nó k thể biết được trước đó, name
cần tìm thuộc country
nào.
Ngoài việc có thể tăng tốc các câu truy vấn với toán tử Ở trên tôi có đề cập là index sẽ được tạo tự động một số trường hợp như khi tạo hay Còn đây là cú pháp khi muốn tạo INDEX riêng: và ví dụ tạo index multiple columns: UNIQUE INDEX với 3 columns : user_id, bank_id, account_id sẽ tăng tốc độ với những truy vấn có điều kiện như: và sẽ vô dụng với các kiểu truy vấn như:=
thì B-tree index còn có thể đối ứng với các toán tử như >, insert data --> sau khi insert hoàn thành thì chúng ta tạo lại index, đây cũng là một solution tốt trong một vài trường hợp nhất định.
Một số cách tạo index đơn giản
Primarykey
ALTER TABLE tbl_name ADD PRIMARY KEY [column_list]
UNIQUE
ALTER TABLE tbl_name ADD UNIQUE index_name [column_list]
ALTER TABLE tbl_name ADD INDEX index_name [column_list]
hoặc:
CREATE UNIQUE INDEX index_name ON table_name [column_list];
UNIQUE INDEX
[có ý nghĩa như khi tạo UNIQUE]ALTER TABLE tbl_name ADD UNIQUE INDEX index_name [column_list]
CREATE TABLE `wallets` [
`id` int not null AUTO_INCREMENT, primary key [`id`],
`user_id` int[10] unsigned NOT NULL,
`bank_id` int[10] unsigned NOT NULL,
`account_id` int[10] unsigned NOT NULL,
`amount` int[11] NOT NULL DEFAULT 0,
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
UNIQUE INDEX multiple_index [user_id, bank_id, account_id]
];
WHERE user_id = 2 and bank_id = 3 and account_id = 4;
WHERE user_id = 2 and banker_id = 3;
WHERE user_id = 2;
WHERE user_id = 2 and account_id = 4; [do không có column bank_id ở giữa, mất thứ tự columns trong index nên index trong trường hợp này sẽ apply cho column user_id, tương tự như khi chỉ truy vấn với user_id]
WHERE bank_id = 3 and account_id = 4;
WHERE banker_id = 3;
WHERE account_id = 4;
Nếu bạn để ý một chút thì index sẽ chỉ apply với những trường hợp có điều kiện user_id đầu tiên, đó là lý do bạn phải hết sức chú ý về thứ tự columns khi tạo index.
Trong case này, bạn không cần tạo index cho column user_id hay [user_id, bank_id], nó sẽ là thừa và gây tốn dung lượng vì index multiple_index đã bao gồm chúng
Cùng xem lại kết quả qua trình truy vấn từ không đến có index nhé [ví dụ với bảng wallets
có hơn 100K records]
Chưa có INDEX:
EXPLAIN SELECT * FROM wallets WHERE user_id = 253 AND bank_id = 759982 AND account_id = 47
1 | SIMPLE | wallets | ALL | 105908 | 100 | Using where |
Số rows cần phải duyệt là 105908 mới tìm thấy bản ghi tương ứng và không có possible_keys nào được sử dụng. Thử tưởng tượng nếu table có 1 triệu hay nhiều hơn thì sao nhỉ? Quá kinh khủng phải không nào. [Nếu mọi người chưa biết hiểu rõ các thống số khi explain thì có thể tham khảo tại đây]
Sau khi có UNIQUE INDEX multiple_index [user_id, bank_id, account_id]:
EXPLAIN SELECT * FROM wallets WHERE user_id = 253 AND bank_id = 759982 AND account_id = 47
1 | SIMPLE | wallets | const | multiple_index | multiple_index | 12 | const,const,const | 1 | 100 |
Bạn thấy không, sau khi đánh INDEX việc tìm kiếm đã giảm xuống còn 1 row khi sử dụng index [key] là multiple_index. Xem tiếp vài ví dụ với index nhé.
EXPLAIN SELECT * FROM wallets WHERE user_id = 253 AND bank_id = 759982
1 | SIMPLE | wallets | ref | multiple_index | multiple_index | 8 | const,const | 41 | 100 |
SELECT count[*] FROM wallets WHERE user_id = 253 AND bank_id = 759982
41 |
Có 41 kết quả của câu truy vấn, trùng khớp với số rows cần duyệt để tìm kiếm.
EXPLAIN SELECT * FROM wallets WHERE user_id = 253
1 | SIMPLE | wallets | ref | multiple_index | multiple_index | 4 | const | 246 | 100 |
SELECT count[*] FROM wallets WHERE user_id = 253
246 |
Tương tự như trên, có 246 kết quả của cau truy vấn, trùng với số rows cầ duyệt để tìm kiếm, bạn đã thấy sức mình của index chưa nào?
EXPLAIN SELECT * FROM wallets WHERE user_id = 253 AND account_id = 47
1 | SIMPLE | wallets | ref | multiple_index | multiple_index | 4 | const | 246 | 100 |
SELECT count[*] FROM wallets WHERE user_id = 253 AND account_id = 47
6 |
Bạn thấy không, câu query chỉ có có 6 kết quả. tuy nhiên khi EXPLAIN
thì cần duyệt qua 246 bản ghi, lý do là trong câu điều kiện truy vấn, đã bỏ mất column bank_id
trong index, không còn giữ đúng thứ tự columns ban đầu [user_id, bank_id, account_id]. Vì vậy explain có kết quả tương tự như khi WHERE user_id = 253
Tổng kết
Trên đây mình đã giới thiệu với tất cả mọi người về việc cách sử dụng INDEX
làm sao cho hiệu quả nhất. Việc hiểu và vận dụng chúng là điều
vô cùng quan trọng, hãy cẩn thận với con dao hai lưỡi này. Ngoài ra, để mài bóng con dao
này, để performance tăng lên gấp bội
lần khi gặp bài toán data cực lớn lên cỡ vài chục triệu records, hãy kết hợp sử dụng index với Partitions
. Để tìm hiểu về partitions
mọi người có thể xem bài viết của mình tại đây Cám ơn mọi người đã dành thời gian đọc bài viết, nếu có bất cứ vấn
đề gì, đừng ngại thắc mắc dưới phần bình luận, mình cùng nhau chia sẻ kiến thức, cùng nhau nâng cao trình độ, và nhớ tặng mình 1 vote up
nhé. Tks!!!
Tài liệu tham khảo
//www.vertabelo.com/blog/all-about-indexes-the-very-basics/
//www.vertabelo.com/blog/all-about-indexes-part-2-mysql-index-structure-and-performance/