Có thực sự cần thiết cho tất cả các cột được chọn để được lập chỉ mục để MySQL chọn sử dụng chỉ mục không?
Đây là một câu hỏi được tải bởi vì có những yếu tố xác định liệu một chỉ mục có đáng sử dụng hay không.
YẾU TỐ # 1
Đối với bất kỳ chỉ số nhất định, dân số chính là gì? Nói cách khác, cardinality (số lượng riêng biệt) của tất cả các bộ dữ liệu được ghi trong chỉ mục là gì?
YẾU TỐ # 2
Bạn đang sử dụng công cụ lưu trữ nào? Có phải tất cả các cột cần thiết có thể truy cập từ một chỉ mục?
CÁI GÌ TIẾP THEO ???
Hãy lấy một ví dụ đơn giản: một bảng chứa hai giá trị (Nam và Nữ)
Hãy tạo một bảng như vậy với một bài kiểm tra để sử dụng chỉ mục
USE test
DROP TABLE IF EXISTS mf;
CREATE TABLE mf
(
id int not null auto_increment,
gender char(1),
primary key (id),
key (gender)
) ENGINE=InnODB;
INSERT INTO mf (gender) VALUES
('M'),('M'),('M'),('M'),('M'),('M'),('M'),('M'),
('M'),('M'),('M'),('M'),('F'),('F'),('M'),('M'),
('M'),('M'),('M'),('M'),('M'),('M'),('M'),('M'),
('M'),('M'),('M'),('M'),('M'),('M'),('M'),('M'),
('F'),('M'),('M'),('M'),('M'),('M'),('M'),('M');
ANALYZE TABLE mf;
EXPLAIN SELECT gender FROM mf WHERE gender='F';
EXPLAIN SELECT gender FROM mf WHERE gender='M';
EXPLAIN SELECT id FROM mf WHERE gender='F';
EXPLAIN SELECT id FROM mf WHERE gender='M';
KIỂM TRA InnoDB
mysql> USE test
Database changed
mysql> DROP TABLE IF EXISTS mf;
Query OK, 0 rows affected (0.00 sec)
mysql> CREATE TABLE mf
-> (
-> id int not null auto_increment,
-> gender char(1),
-> primary key (id),
-> key (gender)
-> ) ENGINE=InnoDB;
Query OK, 0 rows affected (0.07 sec)
mysql> INSERT INTO mf (gender) VALUES
-> ('M'),('M'),('M'),('M'),('M'),('M'),('M'),('M'),
-> ('M'),('M'),('M'),('M'),('F'),('F'),('M'),('M'),
-> ('M'),('M'),('M'),('M'),('M'),('M'),('M'),('M'),
-> ('M'),('M'),('M'),('M'),('M'),('M'),('M'),('M'),
-> ('F'),('M'),('M'),('M'),('M'),('M'),('M'),('M');
Query OK, 40 rows affected (0.06 sec)
Records: 40 Duplicates: 0 Warnings: 0
mysql> ANALYZE TABLE mf;
+---------+---------+----------+----------+
| Table | Op | Msg_type | Msg_text |
+---------+---------+----------+----------+
| test.mf | analyze | status | OK |
+---------+---------+----------+----------+
1 row in set (0.00 sec)
mysql> EXPLAIN SELECT gender FROM mf WHERE gender='F';
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
| 1 | SIMPLE | mf | ref | gender | gender | 2 | const | 3 | Using where; Using index |
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
1 row in set (0.00 sec)
mysql> EXPLAIN SELECT gender FROM mf WHERE gender='M';
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
| 1 | SIMPLE | mf | ref | gender | gender | 2 | const | 37 | Using where; Using index |
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
1 row in set (0.00 sec)
mysql> EXPLAIN SELECT id FROM mf WHERE gender='F';
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
| 1 | SIMPLE | mf | ref | gender | gender | 2 | const | 3 | Using where; Using index |
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
1 row in set (0.00 sec)
mysql> EXPLAIN SELECT id FROM mf WHERE gender='M';
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
| 1 | SIMPLE | mf | ref | gender | gender | 2 | const | 37 | Using where; Using index |
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
1 row in set (0.00 sec)
mysql>
KIỂM TRA MyISAM
mysql> USE test
Database changed
mysql> DROP TABLE IF EXISTS mf;
Query OK, 0 rows affected (0.00 sec)
mysql> CREATE TABLE mf
-> (
-> id int not null auto_increment,
-> gender char(1),
-> primary key (id),
-> key (gender)
-> ) ENGINE=MyISAM;
Query OK, 0 rows affected (0.05 sec)
mysql> INSERT INTO mf (gender) VALUES
-> ('M'),('M'),('M'),('M'),('M'),('M'),('M'),('M'),
-> ('M'),('M'),('M'),('M'),('F'),('F'),('M'),('M'),
-> ('M'),('M'),('M'),('M'),('M'),('M'),('M'),('M'),
-> ('M'),('M'),('M'),('M'),('M'),('M'),('M'),('M'),
-> ('F'),('M'),('M'),('M'),('M'),('M'),('M'),('M');
Query OK, 40 rows affected (0.00 sec)
Records: 40 Duplicates: 0 Warnings: 0
mysql> ANALYZE TABLE mf;
+---------+---------+----------+----------+
| Table | Op | Msg_type | Msg_text |
+---------+---------+----------+----------+
| test.mf | analyze | status | OK |
+---------+---------+----------+----------+
1 row in set (0.00 sec)
mysql> EXPLAIN SELECT gender FROM mf WHERE gender='F';
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
| 1 | SIMPLE | mf | ref | gender | gender | 2 | const | 3 | Using where; Using index |
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
1 row in set (0.00 sec)
mysql> EXPLAIN SELECT gender FROM mf WHERE gender='M';
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
| 1 | SIMPLE | mf | ref | gender | gender | 2 | const | 36 | Using where; Using index |
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
1 row in set (0.00 sec)
mysql> EXPLAIN SELECT id FROM mf WHERE gender='F';
+----+-------------+-------+------+---------------+--------+---------+-------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+--------+---------+-------+------+-------------+
| 1 | SIMPLE | mf | ref | gender | gender | 2 | const | 3 | Using where |
+----+-------------+-------+------+---------------+--------+---------+-------+------+-------------+
1 row in set (0.00 sec)
mysql> EXPLAIN SELECT id FROM mf WHERE gender='M';
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
| 1 | SIMPLE | mf | ALL | gender | NULL | NULL | NULL | 40 | Using where |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
1 row in set (0.00 sec)
mysql>
Phân tích cho InnoDB
Khi dữ liệu được tải dưới dạng InnoDB, xin lưu ý rằng cả bốn gói đều EXPLAIN
sử dụng gender
chỉ mục. Các EXPLAIN
kế hoạch thứ ba và thứ tư đã sử dụng gender
chỉ mục mặc dù dữ liệu được yêu cầu là id
. Tại sao? Bởi vì id
trong PRIMARY KEY
và tất cả các chỉ mục phụ đều có con trỏ tham chiếu trở lại PRIMARY KEY
(thông qua gen_clust_index ).
Phân tích cho MyISAM
Khi dữ liệu được tải dưới dạng MyISAM, xin lưu ý rằng ba gói đầu tiên đã EXPLAIN
sử dụng gender
chỉ mục. Trong EXPLAIN
kế hoạch thứ tư , Trình tối ưu hóa truy vấn quyết định hoàn toàn không sử dụng một chỉ mục. Nó đã chọn để quét toàn bộ bảng thay thế. Tại sao?
Bất kể DBMS, Trình tối ưu hóa truy vấn hoạt động theo nguyên tắc rất đơn giản: Nếu một chỉ mục đang được sàng lọc như một ứng cử viên được sử dụng để thực hiện tra cứu và Trình tối ưu hóa truy vấn tính toán rằng nó phải tra cứu hơn 5% tổng số các hàng trong bảng:
- quét chỉ mục đầy đủ được thực hiện nếu tất cả các cột cần thiết để truy xuất đều nằm trong chỉ mục được chọn
- quét toàn bộ bảng
PHẦN KẾT LUẬN
Nếu bạn không có chỉ số bao phủ phù hợp hoặc nếu dân số chính cho bất kỳ bộ dữ liệu đã cho nào chiếm hơn 5% của bảng, thì sáu điều phải xảy ra:
- Hãy nhận ra rằng bạn phải lập hồ sơ các truy vấn
- Tìm tất cả
WHERE
, GROUP BY
và ĐẶT HÀNG BY` mệnh đề từ các Truy vấn đó
- Xây dựng các chỉ mục theo thứ tự này
WHERE
cột mệnh đề với các giá trị tĩnh
GROUP BY
cột
ORDER BY
cột
- Tránh quét toàn bộ bảng (Truy vấn thiếu một
WHERE
mệnh đề hợp lý )
- Tránh các quần thể khóa xấu (hoặc ít nhất là lưu trữ các quần thể khóa xấu đó)
- Quyết định về Công cụ lưu trữ MySQL tốt nhất ( InnoDB hoặc MyISAM ) cho các Bảng
Tôi đã viết về quy tắc 5% này trong quá khứ:
CẬP NHẬT 2012-11-14 13:05 EDT
Tôi đã xem lại câu hỏi của bạn và tại bài viết SO ban đầu . Sau đó, tôi nghĩ về tôi Analysis for InnoDB
đã đề cập trước đó. Nó trùng với cái person
bàn. Tại sao?
Cho cả hai bảng mf
vàperson
- Công cụ lưu trữ là InnoDB
- Khóa chính là
id
- Bảng truy cập là theo chỉ số phụ
- Nếu bảng là MyISAM, chúng ta sẽ thấy một
EXPLAIN
kế hoạch hoàn toàn khác
Bây giờ, hãy xem truy vấn từ câu hỏi SO : select * from person order by age\G
. Vì không có WHERE
mệnh đề, bạn rõ ràng yêu cầu quét toàn bộ bảng . Thứ tự sắp xếp mặc định của bảng sẽ là id
(PRIMARY KEY) vì tính năng auto_increment của nó và gen_clust_index (còn gọi là Clustered Index) được sắp xếp theo thứ tự hàng . Khi bạn đặt hàng theo chỉ mục, hãy nhớ rằng các chỉ mục phụ của InnoDB có hàng được gắn vào mỗi mục nhập chỉ mục. Điều này tạo ra nhu cầu nội bộ để truy cập hàng đầy đủ mỗi lần.
Thiết lập ORDER BY
trên bảng InnoDB có thể là một nhiệm vụ khá khó khăn nếu bạn bỏ qua những sự thật này về cách tổ chức các chỉ mục InnoDB.
Quay trở lại truy vấn SO đó, vì bạn rõ ràng yêu cầu quét toàn bộ bảng , IMHO Trình tối ưu hóa truy vấn MySQL đã làm điều đúng (hoặc ít nhất, đã chọn đường dẫn ít kháng cự nhất). Khi nói đến InnoDB và truy vấn SO, việc thực hiện quét toàn bộ bảng sẽ dễ dàng hơn nhiều so với thực hiện quét filesort
toàn bộ chỉ mục và tra cứu hàng qua gen_clust_index cho mỗi mục nhập chỉ mục phụ.
Tôi không phải là người ủng hộ việc sử dụng Gợi ý Index vì nó bỏ qua kế hoạch GIẢI THÍCH. Mặc dù vậy, nếu bạn thực sự biết dữ liệu của mình tốt hơn InnoDB, bạn sẽ phải dùng đến Gợi ý Chỉ mục, đặc biệt là với các truy vấn không có WHERE
mệnh đề.
CẬP NHẬT 2012-11-14 14:21 EDT
Theo cuốn sách Tìm hiểu về Nội bộ MySQL
Trang 202 Đoạn 7 nói như sau:
Dữ liệu được lưu trữ trong một cấu trúc đặc biệt gọi là chỉ mục được nhóm , là cây B với khóa chính đóng vai trò là giá trị khóa và bản ghi thực tế (chứ không phải là một con trỏ) trong phần dữ liệu. Do đó, mỗi bảng InnoDB phải có khóa chính. Nếu không được cung cấp, một cột ID hàng đặc biệt thường không hiển thị cho người dùng sẽ được thêm vào để hoạt động như một khóa chính. Khóa phụ sẽ lưu trữ giá trị của khóa chính xác định bản ghi. Mã cây B có thể được tìm thấy trong innobase / btr / btr0btr.c .
Đây là lý do tại sao tôi đã tuyên bố trước đó: việc thực hiện quét toàn bộ bảng và sau đó một số tệp sẽ dễ dàng hơn nhiều so với thực hiện quét chỉ mục đầy đủ và tra cứu hàng qua gen_clust_index cho mỗi mục nhập chỉ mục phụ . InnoDB sẽ thực hiện tra cứu chỉ mục kép mỗi lần . Nghe có vẻ tàn bạo, nhưng đó chỉ là sự thật. Một lần nữa, hãy xem xét việc thiếu WHERE
điều khoản. Bản thân nó là gợi ý cho Trình tối ưu hóa truy vấn MySQL để thực hiện quét toàn bộ bảng.
FOR ORDER BY
(đó là trường hợp cụ thể trong câu hỏi này). Câu hỏi đã nói rằng trong trường hợp này, công cụ lưu trữ làInnoDB
(và câu hỏi SO ban đầu cho thấy các hàng 10k được phân phối khá đồng đều trên 8 mặt hàng, cardinality cũng không phải là vấn đề ở đây). Đáng buồn thay, tôi không nghĩ rằng điều này trả lời câu hỏi.