Tại sao MySQL bỏ qua chỉ số ngay cả khi có hiệu lực cho đơn đặt hàng này?


14

Tôi chạy một EXPLAIN:

mysql> explain select last_name from employees order by last_name;
+----+-------------+-----------+------+---------------+------+---------+------+-------+----------------+  
| id | select_type | table     | type | possible_keys | key  | key_len | ref  | rows  | Extra          |
+----+-------------+-----------+------+---------------+------+---------+------+-------+----------------+  
|  1 | SIMPLE      | employees | ALL  | NULL          | NULL | NULL    | NULL | 10031 | Using filesort |
+----+-------------+-----------+------+---------------+------+---------+------+-------+----------------+  
1 row in set (0.00 sec)  

Các chỉ mục trong bảng của tôi:

mysql> show index from employees;  
+-----------+------------+---------------+--------------+---------------+-----------+-------------+----------+--------+------+------------+---------+---------------+  
| Table     | Non_unique | Key_name      | Seq_in_index | Column_name   | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |  
+-----------+------------+---------------+--------------+---------------+-----------+-------------+----------+--------+------+------------+---------+---------------+  
| employees |          0 | PRIMARY       |            1 | subsidiary_id | A         |           6 |     NULL | NULL   |      | BTREE      |         |               |  
| employees |          0 | PRIMARY       |            2 | employee_id   | A         |       10031 |     NULL | NULL   |      | BTREE      |         |               |  
| employees |          1 | idx_last_name |            1 | last_name     | A         |       10031 |      700 | NULL   |      | BTREE      |         |               |  
| employees |          1 | date_of_birth |            1 | date_of_birth | A         |       10031 |     NULL | NULL   | YES  | BTREE      |         |               |  
| employees |          1 | date_of_birth |            2 | subsidiary_id | A         |       10031 |     NULL | NULL   |      | BTREE      |         |               |  
+-----------+------------+---------------+--------------+---------------+-----------+-------------+----------+--------+------+------------+---------+---------------+  
5 rows in set (0.02 sec)  

Có một chỉ mục trên last_name nhưng trình tối ưu hóa không sử dụng nó.
Vì vậy tôi làm:

mysql> explain select last_name from employees force index(idx_last_name) order by last_name;  
+----+-------------+-----------+------+---------------+------+---------+------+-------+----------------+  
| id | select_type | table     | type | possible_keys | key  | key_len | ref  | rows  | Extra          |  
+----+-------------+-----------+------+---------------+------+---------+------+-------+----------------+  
|  1 | SIMPLE      | employees | ALL  | NULL          | NULL | NULL    | NULL | 10031 | Using filesort |  
+----+-------------+-----------+------+---------------+------+---------+------+-------+----------------+  
1 row in set (0.00 sec)  

Nhưng vẫn chỉ số được không sử dụng! Tôi làm gì sai ở đây?
Liệu nó có liên quan đến thực tế là chỉ số này NON_UNIQUEkhông? BTW tên cuối cùng làVARCHAR(1000)

Cập nhật được yêu cầu bởi @RolandoMySQLDBA

mysql> SELECT COUNT(DISTINCT last_name) DistinctCount FROM employees;  
+---------------+  
| DistinctCount |  
+---------------+  
|         10000 |  
+---------------+  
1 row in set (0.05 sec)  


mysql> SELECT COUNT(1) FROM (SELECT COUNT(1) Count500,last_name FROM employees GROUP BY last_name HAVING COUNT(1) > 500) A;  
+----------+  
| COUNT(1) |  
+----------+  
|        0 |  
+----------+  
1 row in set (0.15 sec)  

Vui lòng chạy hai truy vấn sau: 1) SELECT COUNT(DISTINCT last_name) DistinctCount FROM employees;2) SELECT COUNT(1) FROM (SELECT COUNT(1) Count500,last_name FROM employees GROUP BY last_name HAVING COUNT(1) > 500) A;. Kết quả của mỗi lần đếm là gì?
RolandoMySQLDBA

@RolandoMySQLDBA: Tôi đã cập nhật OP với thông tin bạn yêu cầu.
Cratylus

Hai truy vấn nữa, xin vui lòng: 1) SELECT COUNT(1) FullTableCount FROM employees;và 2) SELECT * FROM (SELECT COUNT(1) Count500,last_name FROM employees GROUP BY last_name HAVING COUNT(1) > 500) A LIMIT 10;.
RolandoMySQLDBA

Không sao, tôi thấy giải thích với những gì tôi cần.
RolandoMySQLDBA

2
@Cratylus bạn đã chấp nhận một câu trả lời sai, bạn nên chấp nhận câu trả lời
miracle173

Câu trả lời:


6

VẤN ĐỀ # 1

Nhìn vào truy vấn

select last_name from employees order by last_name;

Tôi không thấy mệnh đề WHERE có ý nghĩa và Trình tối ưu hóa truy vấn MySQL cũng không. Không có khuyến khích để sử dụng một chỉ số.

VẤN ĐỀ # 2

Nhìn vào truy vấn

select last_name from employees force index(idx_last_name) order by last_name; 

Bạn đã cho nó một chỉ mục, nhưng Opitmizer truy vấn đã tiếp quản. Tôi đã thấy hành vi này trước đây ( Làm cách nào để buộc THAM GIA sử dụng một chỉ mục cụ thể trong MySQL? )

Tại sao điều này nên xảy ra?

Không có WHEREmệnh đề, Trình tối ưu hóa truy vấn sẽ nói như sau:

  • Đây là Bảng InnoDB
  • Đó là một cột được lập chỉ mục
  • Chỉ mục có row_id của gen_clust_index (còn gọi là Chỉ mục cụm)
  • Tại sao tôi nên nhìn vào chỉ số khi
    • không có WHEREđiều khoản?
    • Tôi sẽ luôn phải quay trở lại bàn?
  • Vì tất cả các hàng trong bảng InnoDB nằm trong cùng một khối 16K như gen_clust_index, nên tôi sẽ thực hiện quét toàn bộ bảng thay thế.

Trình tối ưu hóa truy vấn đã chọn đường dẫn ít kháng cự nhất.

Bạn sẽ gặp một chút sốc, nhưng rồi đây: Bạn có biết rằng Trình tối ưu hóa truy vấn sẽ xử lý MyISAM hoàn toàn khác không?

Có lẽ bạn đang nói HUH ???? LÀM SAO ????

MyISAM lưu trữ dữ liệu trong một .MYDtệp và tất cả các chỉ mục trong .MYItệp.

Cùng một truy vấn sẽ tạo ra một kế hoạch GIẢI THÍCH khác vì chỉ mục nằm trong một tệp khác với dữ liệu. Tại sao ? Đây là lý do tại sao:

  • Dữ liệu cần thiết ( last_namecột) đã được sắp xếp trong.MYI
  • Trong trường hợp xấu nhất, bạn sẽ quét toàn bộ chỉ mục
  • Bạn sẽ chỉ truy cập vào cột last_nametừ chỉ mục
  • Bạn không cần phải lọc qua không mong muốn
  • Bạn sẽ không kích hoạt tạo tệp tạm thời để sắp xếp

Làm thế nào có thể chắc chắn về điều này? Tôi đã thử nghiệm lý thuyết làm việc này về cách sử dụng một bộ lưu trữ khác nhau sẽ tạo ra một gói EXPLAIN khác (đôi khi là tốt hơn): Phải có một chỉ mục bao gồm tất cả các cột được chọn để sử dụng cho ORDER BY?


1
-1 @Rolando câu trả lời này không kém chính xác hơn câu trả lời đúng của Michael-sqlbot nhưng nó sai, ví dụ: hướng dẫn sử dụng nói: "MySQL sử dụng các chỉ mục cho các hoạt động này: (...) Để sắp xếp hoặc nhóm một bảng nếu sắp xếp hoặc việc nhóm được thực hiện trên tiền tố ngoài cùng bên trái của một chỉ mục có thể sử dụng (...) ". Ngoài ra một số tuyên bố khác của bài viết của bạn là tranh chấp. Tôi khuyên bạn nên xóa câu trả lời này hoặc làm lại nó.
phép lạ173

Câu trả lời này không đúng. Một chỉ mục vẫn có thể được sử dụng ngay cả khi không có mệnh đề WHERE nếu nó tránh sắp xếp.
vào

19

Trên thực tế, vấn đề ở đây là nó trông giống như một chỉ số tiền tố. Tôi không thấy định nghĩa bảng trong câu hỏi, nhưng sub_part= 700? Bạn chưa lập chỉ mục cho toàn bộ cột, vì vậy chỉ mục này không thể được sử dụng để sắp xếp và cũng không hữu ích như một chỉ mục bao trùm. Nó chỉ có thể được sử dụng để tìm các hàng "có thể" khớp với a WHEREvà lớp máy chủ (phía trên công cụ lưu trữ) sẽ phải lọc thêm các hàng khớp. Bạn có thực sự cần 1000 ký tự cho một tên cuối cùng?


cập nhật để minh họa: Tôi có một bảng kiểm tra bảng với hơn 500 hàng trong đó, mỗi bảng có tên miền của một trang web trong một cột domain_name VARCHAR(254) NOT NULLvà không có chỉ mục.

mysql> alter table keydemo add key(domain_name);
Query OK, 0 rows affected (0.17 sec)
Records: 0  Duplicates: 0  Warnings: 0

Với cột đầy đủ được lập chỉ mục, truy vấn sử dụng chỉ mục:

mysql> explain select domain_name from keydemo order by domain_name;
+----+-------------+---------+-------+---------------+-------------+---------+------+------+-------------+
| id | select_type | table   | type  | possible_keys | key         | key_len | ref  | rows | Extra       |
+----+-------------+---------+-------+---------------+-------------+---------+------+------+-------------+
|  1 | SIMPLE      | keydemo | index | NULL          | domain_name | 764     | NULL |  541 | Using index |
+----+-------------+---------+-------+---------------+-------------+---------+------+------+-------------+
1 row in set (0.01 sec)

Vì vậy, bây giờ, tôi sẽ bỏ chỉ mục đó và chỉ lập chỉ mục 200 ký tự đầu tiên của domain_name.

mysql> alter table keydemo drop key domain_name;
Query OK, 0 rows affected (0.11 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> alter table keydemo add key(domain_name(200));
Query OK, 0 rows affected (0.08 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> explain select domain_name from keydemo order by domain_name;
+----+-------------+---------+------+---------------+------+---------+------+------+----------------+
| id | select_type | table   | type | possible_keys | key  | key_len | ref  | rows | Extra          |
+----+-------------+---------+------+---------------+------+---------+------+------+----------------+
|  1 | SIMPLE      | keydemo | ALL  | NULL          | NULL | NULL    | NULL |  541 | Using filesort |
+----+-------------+---------+------+---------------+------+---------+------+------+----------------+
1 row in set (0.00 sec)

mysql>

Voila.

Cũng lưu ý rằng chỉ mục, ở 200 ký tự, dài hơn giá trị dài nhất trong cột ...

mysql> select max(length(domain_name)) from keydemo;
+--------------------------+
| max(length(domain_name)) |
+--------------------------+
|                       43 |
+--------------------------+
1 row in set (0.04 sec)

... nhưng điều đó không tạo nên bất kỳ sự khác biệt nào. Chỉ có thể sử dụng một chỉ mục được khai báo với độ dài tiền tố để tra cứu, không phải để sắp xếp và không phải là chỉ mục bao phủ, vì theo định nghĩa, nó không chứa giá trị cột đầy đủ.

Ngoài ra, các truy vấn trên được chạy trên bảng InnoDB, nhưng chạy chúng trên bảng MyISAM mang lại kết quả gần như giống hệt nhau. Sự khác biệt duy nhất trong trường hợp này là số lượng InnoDB rowshơi bị tắt (541) trong khi MyISAM hiển thị số hàng chính xác (563) là hành vi bình thường do hai công cụ lưu trữ xử lý chỉ số lặn rất khác nhau.

Tôi vẫn sẽ khẳng định rằng cột last_name có thể lớn hơn mức cần thiết, nhưng vẫn có thể lập chỉ mục cho toàn bộ cột, nếu bạn đang sử dụng InnoDB và chạy MySQL 5.5 hoặc 5.6:

Theo mặc định, khóa chỉ mục cho chỉ mục một cột có thể lên tới 767 byte. Giới hạn độ dài tương tự áp dụng cho bất kỳ tiền tố khóa chỉ mục. Xem Phần 13.1.13, CREATE INDEXSyntax trực tiếp. Ví dụ: bạn có thể đạt giới hạn này với chỉ số tiền tố cột gồm hơn 255 ký tự trên một TEXThoặc một VARCHARcột, giả sử một UTF-8bộ ký tự và tối đa 3 byte cho mỗi ký tự. Khi innodb_large_prefixtùy chọn cấu hình được bật, giới hạn độ dài này được tăng lên 3072 byte, đối với InnoDBcác bảng sử dụng định dạng DYNAMICCOMPRESSEDhàng.

- http://dev.mysql.com/doc/refman/5.5/en/innodb-restrictions.html


Quan điểm thú vị. Cột là varchar(1000)nhưng điều này vượt quá mức tối đa được phép cho chỉ số là ~ 750
Cratylus

8
Câu trả lời này nên được chấp nhận.
ypercubeᵀᴹ

1
@ypercube Câu trả lời này chính xác hơn của tôi. +1 cho bình luận của bạn và +1 cho câu trả lời này. Có thể điều này nên được chấp nhận thay vì trên tôi.
RolandoMySQLDBA

1
@Timo, đó là một câu hỏi thú vị ... mà tôi muốn đề xuất là một câu hỏi mới, ở đây, có lẽ với một liên kết đến câu trả lời này, cho bối cảnh. Đăng kết quả đầu ra hoàn chỉnh từ EXPLAIN SELECT ..., cũng như SHOW CREATE TABLE ...SELECT @@VERSION;vì các thay đổi đối với trình tối ưu hóa giữa các phiên bản có thể có liên quan.
Michael - sqlbot

1
Đến bây giờ tôi có thể báo cáo rằng (ít nhất là 5,7) một chỉ số tiền tố không giúp ích gì cho việc lập chỉ mục null, như tôi đã yêu cầu trong nhận xét của mình ở trên.
Timo

2

Tôi đã đưa ra câu trả lời vì một nhận xét sẽ không hỗ trợ cho việc định dạng và RolandoMySQL DBA đã nói về gen_clust_index và innodb. Và điều này là rất quan trọng trên một bảng dựa trên innodb. Điều này đi xa hơn kiến ​​thức DBA bình thường vì bạn cần có khả năng phân tích mã C ..

Bạn nên LUÔN LUÔN LUÔN tạo ra một KHÓA CHÍNH hoặc một KHÓA ĐỘC ĐÁO nếu bạn đang sử dụng Innodb. Nếu bạn không innodb sẽ sử dụng ROW_ID do chính nó tạo ra, điều này có thể gây hại cho bạn nhiều hơn là có lợi.

Tôi sẽ cố gắng giải thích nó dễ dàng vì bằng chứng dựa trên mã C.

/**********************************************************************//**
Returns a new row id.
@return the new id */
UNIV_INLINE
row_id_t
dict_sys_get_new_row_id(void)
/*=========================*/
{
    row_id_t    id;

    mutex_enter(&(dict_sys->mutex));

    id = dict_sys->row_id;

    if (0 == (id % DICT_HDR_ROW_ID_WRITE_MARGIN)) {
          dict_hdr_flush_row_id();
    }

    dict_sys->row_id++;
    mutex_exit(&(dict_sys->mutex));
    return(id);
}

Vấn đề đầu tiên

mutex_enter (& (dict_sys-> mutex));

Dòng này đảm bảo chỉ có một luồng có thể truy cập dict_sys-> mutex cùng một lúc. Điều gì sẽ xảy ra nếu giá trị đã bị thay đổi ... vâng, một luồng phải chờ để bạn có được một tính năng ngẫu nhiên tốt đẹp như khóa luồng hoặc nếu bạn có nhiều bảng hơn mà không có PRIMARY KEY hoặc UNIQUE KEY của riêng bạn thì bạn sẽ có một tính năng hay với khóa bàn của innodb ' không phải là lý do tại sao MyISAM được thay thế bởi InnoDB vì tắt tính năng hay được gọi là khóa dựa trên bản ghi / hàng ..

Vấn đề thứ hai

(0 == (id% DICT_HDR_law_ID_WRITE_MARGIN))

Tính toán modulo (%) chậm không tốt nếu bạn chèn hàng loạt vì nó cần được tính toán lại mỗi lần ... và bởi vì DICT_HDR_law_ID_WRITE_MARGIN (giá trị 256) là sức mạnh của hai điều này có thể được thực hiện nhanh hơn nhiều ..

(0 == (id & (DICT_HDR_law_ID_WRITE_MARGIN - 1)))

Lưu ý phụ nếu trình biên dịch C được cấu hình để tối ưu hóa và đó là một trình tối ưu hóa tốt, trình tối ưu hóa C sẽ sửa mã "nặng" thành phiên bản nhẹ hơn

phương châm của câu chuyện luôn tạo ra KHÓA CHÍNH CỦA BẠN hoặc đảm bảo bạn có chỉ số ĐỘC ĐÁO khi bạn tạo bảng từ đầu


Thêm sao chép dựa trên hàng và thực tế là ID hàng không nhất quán trên các máy chủ và quan điểm của Raymond về việc luôn tạo khóa chính thậm chí còn quan trọng hơn.

Xin đừng cho rằng UNIQUElà đủ - nó cũng cần phải chỉ bao gồm các cột không NULL cho chỉ số duy nhất được đề bạt lên PK.
Rick James

"tính toán modulo (%) là chậm" - Quan trọng hơn là phần trăm thời gian của một INSERTchi tiêu được sử dụng trong chức năng này. Tôi nghi ngờ là không đáng kể. Tương phản với nỗ lực xẻng các cột xung quanh, thực hiện các thao tác BTree, bao gồm cả việc tách khối thỉnh thoảng, các biến thể khác nhau trên bộ đệm_pool, công cụ đệm thay đổi, v.v.
Rick James

Đúng @RickJames chi phí có thể rất nhỏ nhưng nhiều số nhỏ cũng cộng lại (vẫn sẽ là tối ưu hóa vi mô) .. Bên cạnh đó, vấn đề đầu tiên là rắc rối nhất
Raymond Nijland
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.