Phải có một chỉ mục bao gồm tất cả các cột được chọn để nó được sử dụng cho ĐẶT HÀNG B? NG?


15

Ở SO, gần đây có người hỏi Tại sao không ĐẶT HÀNG bằng cách sử dụng chỉ mục?

Tình huống liên quan đến một bảng InnoDB đơn giản trong MySQL bao gồm ba cột và 10k hàng. Một trong những cột, một số nguyên, đã được lập chỉ mục và OP tìm cách lấy toàn bộ bảng được sắp xếp trên cột đó:

SELECT * FROM person ORDER BY age

Ông đã đính kèm kết EXPLAINquả đầu ra cho thấy truy vấn này đã được giải quyết bằng filesort(chứ không phải chỉ mục) và hỏi tại sao lại như vậy.

Mặc dù gợi ý FORCE INDEX FOR ORDER BY (age) khiến chỉ mục được sử dụng , nhưng ai đó đã trả lời (với các bình luận hỗ trợ / upvote từ người khác) rằng một chỉ mục chỉ được sử dụng để sắp xếp khi tất cả các cột được chọn đều được đọc từ chỉ mục (nghĩa là thường được chỉ định Using indextrong Extracột của EXPLAINđầu ra). Một lời giải thích sau đó đã được đưa ra rằng việc duyệt qua chỉ mục và sau đó tìm nạp các cột từ bảng dẫn đến I / O ngẫu nhiên, mà MySQL xem đắt hơn a filesort.

Điều này dường như bay bổng khi đối mặt với chương thủ công về ORDER BYTối ưu hóa , nó không chỉ truyền tải ấn tượng mạnh mà việc thỏa mãn ORDER BYtừ một chỉ số là tốt hơn để thực hiện sắp xếp bổ sung (thực sự, filesortlà sự kết hợp giữa quicksort và sáp nhập và do đó phải có giới hạn thấp hơn ; trong khi đi qua chỉ mục theo thứ tự và tìm kiếm trong bảng nên là điều này có ý nghĩa hoàn hảo), nhưng cũng bỏ qua việc đề cập đến "tối ưu hóa" được cho là này trong khi cũng nêu rõ:Ω(nlog n)O(n)

Các truy vấn sau sử dụng chỉ mục để giải quyết ORDER BYphần:

SELECT * FROM t1
  ORDER BY key_part1,key_part2,... ;

Theo tôi đọc, đó chính xác là trường hợp trong tình huống này (tuy nhiên chỉ mục không được sử dụng mà không có gợi ý rõ ràng).

Câu hỏi của tôi là:

  • 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?

    • Nếu vậy, tài liệu này ở đâu (nếu có)?

    • Nếu không, chuyện gì đang xảy ra ở đây vậy?

Câu trả lời:


14

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 EXPLAINsử dụng genderchỉ mục. Các EXPLAINkế hoạch thứ ba và thứ tư đã sử dụng genderchỉ mục mặc dù dữ liệu được yêu cầu là id. Tại sao? Bởi vì idtrong PRIMARY KEYvà 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 đã EXPLAINsử dụng genderchỉ mục. Trong EXPLAINkế 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:

  1. Hãy nhận ra rằng bạn phải lập hồ sơ các truy vấn
  2. Tìm tất cả WHERE, GROUP BYvà ĐẶT HÀNG BY` mệnh đề từ các Truy vấn đó
  3. 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
  4. Tránh quét toàn bộ bảng (Truy vấn thiếu một WHEREmệnh đề hợp lý )
  5. 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 đó)
  6. 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 personbàn. Tại sao?

Cho cả hai bảng mfperson

  • 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 EXPLAINkế 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ó WHEREmệ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 BYtrê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 filesorttoà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ó WHEREmệ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

nhập mô tả hình ảnh ở đây

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.


Rolando, cảm ơn bạn cho một câu trả lời kỹ lưỡng và chi tiết. Tuy nhiên, nó dường như không liên quan đến việc chọn các chỉ mục 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.
eggyal

Điều này thật thú vị, vì phần đầu tiên cũng là bản năng đầu tiên của tôi (nó không có tính chính xác tốt nên mysql đã chọn sử dụng quét toàn bộ). Nhưng tôi càng đọc, quy tắc đó dường như không được áp dụng cho đơn hàng bằng cách tối ưu hóa. Bạn có chắc chắn nó đặt hàng theo khóa chính cho các chỉ mục cụm innodb? Bài đăng này cho biết khóa chính được thêm vào cuối, vì vậy sắp xếp vẫn nằm trên (các) cột rõ ràng của chỉ mục? Nói tóm lại, tôi vẫn còn bối rối!
Derek Downey

1
Việc filesortlựa chọn được quyết định bởi Trình tối ưu hóa truy vấn vì một lý do đơn giản: Nó thiếu hiểu biết về dữ liệu mà bạn có. Nếu sự lựa chọn của bạn để sử dụng gợi ý chỉ mục (dựa trên vấn đề # 2) mang lại cho bạn sự hài lòng về thời gian chạy, thì bằng mọi cách, hãy thực hiện nó. Câu trả lời tôi cung cấp chỉ là một bài tập học thuật để cho thấy Trình tối ưu hóa truy vấn MySQL có thể bình tĩnh như thế nào cũng như gợi ý các khóa học hành động.
RolandoMySQLDBA

1
Tôi đã đọc và đọc lại thông qua bài viết này và các bài đăng khác, và tôi chỉ có thể đồng ý rằng điều này có liên quan đến việc đặt hàng innodb trên khóa chính vì chúng tôi đang chọn tất cả (chứ không phải chỉ số bao trùm). Tôi ngạc nhiên khi không có đề cập đến sự kỳ quặc cụ thể của InnoDB này trong trang tài liệu tối ưu hóa ORDER BY. Dù sao, +1 cho Rolando
Derek Downey

1
@eggyal Điều này đã được viết trong tuần này. Lưu ý cùng một kế hoạch GIẢI THÍCH và quá trình quét toàn bộ sẽ mất nhiều thời gian hơn nếu bộ dữ liệu không phù hợp với bộ nhớ.
Derek Downey

0

Được điều chỉnh (với sự cho phép) từ câu trả lời của Denis cho một câu hỏi khác trên SO:

Vì tất cả các bản ghi (hoặc gần như tất cả) sẽ được truy vấn tìm nạp, bạn thường tốt hơn là không có chỉ mục nào cả. Lý do cho điều này là, nó thực sự tốn một cái gì đó để đọc một chỉ mục.

Khi bạn chuẩn bị cho toàn bộ bảng, việc đọc tuần tự bảng và sắp xếp các hàng trong bộ nhớ có thể là gói rẻ nhất của bạn. Nếu bạn chỉ cần một vài hàng và hầu hết sẽ khớp với mệnh đề where, việc tìm chỉ mục nhỏ nhất sẽ thực hiện thủ thuật.

Để hiểu lý do tại sao, hình ảnh đĩa I / O liên quan.

Giả sử bạn muốn toàn bộ bảng mà không có chỉ mục. Để làm điều này, bạn đọc data_page1, data_page2, data_page3, v.v., truy cập các trang đĩa khác nhau có liên quan theo thứ tự, cho đến khi bạn đến cuối bảng. Sau đó bạn sắp xếp và trở về.

Nếu bạn muốn 5 hàng trên cùng không có chỉ mục, bạn sẽ tuần tự đọc toàn bộ bảng như trước, trong khi sắp xếp heap 5 hàng trên cùng. Phải thừa nhận rằng, đó là rất nhiều việc đọc và sắp xếp cho một số ít hàng.

Giả sử, bây giờ, bạn muốn toàn bộ bảng với một chỉ mục. Để làm điều này, bạn đọc index_page1, index_page2, v.v., tuần tự. Điều này sau đó dẫn bạn truy cập, giả sử data_page3, sau đó data_page1, sau đó data_page3, sau đó data_page2, v.v., theo thứ tự hoàn toàn ngẫu nhiên (theo đó các hàng được sắp xếp xuất hiện trong dữ liệu). IO liên quan làm cho nó rẻ hơn khi chỉ đọc toàn bộ mớ hỗn độn tuần tự và sắp xếp túi lấy trong bộ nhớ.

Nếu bạn chỉ muốn 5 hàng trên cùng của một bảng được lập chỉ mục, ngược lại, sử dụng chỉ mục sẽ trở thành chiến lược chính xác. Trong trường hợp xấu nhất, bạn tải 5 trang dữ liệu trong bộ nhớ và tiếp tục.

Một trình lập kế hoạch truy vấn SQL tốt, btw, sẽ đưa ra quyết định về việc có nên sử dụng một chỉ mục hay không dựa trên mức độ phân mảnh dữ liệu của bạn. Nếu tìm nạp các hàng theo thứ tự có nghĩa là phóng to qua lại trên bảng, một người lập kế hoạch tốt có thể quyết định rằng nó không đáng để sử dụng chỉ mục. Ngược lại, nếu bảng được phân cụm sử dụng cùng chỉ mục đó, các hàng được đảm bảo theo thứ tự, làm tăng khả năng nó sẽ được sử dụng.

Nhưng sau đó, nếu bạn tham gia cùng một truy vấn với một bảng khác và bảng khác có mệnh đề cực kỳ chọn lọc có thể sử dụng một chỉ mục nhỏ, trình hoạch định có thể quyết định thực sự tốt hơn, ví dụ: tìm nạp tất cả ID của các hàng được gắn thẻ như foo, băm tham gia các bảng và sắp xếp chúng trong bộ nhớ.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.