ทำไม MySQL ไม่สนใจดัชนีแม้จะมีผลบังคับใช้สำหรับการสั่งซื้อนี้ด้วย?


14

ฉันทำงาน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)  

ดัชนีในตารางของฉัน:

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)  

มีดัชนีใน last_name แต่เครื่องมือเพิ่มประสิทธิภาพไม่ได้ใช้
ดังนั้นฉัน:

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)  

แต่ก็ยังไม่ได้ใช้ดัชนี! ฉันทำอะไรผิดที่นี่
มันเกี่ยวข้องกับความจริงที่ว่าดัชนีคือNON_UNIQUEอะไร? BTW นามสกุลคือVARCHAR(1000)

อัปเดตที่ร้องขอโดย @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)  

โปรดเรียกใช้ทั้งสองคำสั่ง: 1) 2)SELECT COUNT(DISTINCT last_name) DistinctCount FROM employees; SELECT COUNT(1) FROM (SELECT COUNT(1) Count500,last_name FROM employees GROUP BY last_name HAVING COUNT(1) > 500) A;ผลลัพธ์ของการนับแต่ละครั้งคืออะไร
RolandoMySQLDBA

@RolandoMySQLDBA: ฉันอัปเดต OP ด้วยข้อมูลที่คุณขอ
Cratylus

สองคำสั่งเพิ่มเติมโปรด: 1) SELECT COUNT(1) FullTableCount FROM employees;และ SELECT * FROM (SELECT COUNT(1) Count500,last_name FROM employees GROUP BY last_name HAVING COUNT(1) > 500) A LIMIT 10;2)
RolandoMySQLDBA

ไม่เป็นไรฉันเห็นคำอธิบายในสิ่งที่ฉันต้องการ
RolandoMySQLDBA

2
@Cratylus คุณยอมรับคำตอบที่ไม่ถูกต้องคุณควรยอมรับคำตอบที่
miracle173

คำตอบ:


6

ปัญหา # 1

ดูที่แบบสอบถาม

select last_name from employees order by last_name;

ฉันไม่เห็นประโยค WHERE ที่มีความหมายและ MySQL Query Optimizer ไม่มีแรงจูงใจให้ใช้ดัชนี

ปัญหา # 2

ดูที่แบบสอบถาม

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

คุณให้ดัชนีกับมัน แต่ Query Opitmizer เข้ามาแทนที่ ฉันเคยเห็นพฤติกรรมนี้มาก่อน ( ฉันจะบังคับให้ JOIN ใช้ดัชนีเฉพาะใน MySQL ได้อย่างไร )

ทำไมสิ่งนี้จึงเกิดขึ้น

ไม่มีWHEREข้อความเครื่องมือเพิ่มประสิทธิภาพการค้นหาจะบอกสิ่งต่อไปนี้กับตัวเอง:

  • นี่คือตาราง InnoDB
  • มันเป็นคอลัมน์ที่จัดทำดัชนี
  • ดัชนีมี row_id ของgen_clust_index (หรือที่เรียกว่า Clustered Index)
  • ทำไมฉันควรดูดัชนีเมื่อ
    • ไม่มีWHEREข้อ?
    • ฉันจะต้องย้อนกลับไปที่โต๊ะเสมอหรือไม่
  • เนื่องจากแถวทั้งหมดในตาราง InnoDB อยู่ในบล็อก 16K เดียวกับ gen_clust_index ฉันจะทำการสแกนตารางเต็มแทน

เครื่องมือเพิ่มประสิทธิภาพ Query เลือกเส้นทางที่มีความต้านทานน้อยที่สุด

คุณกำลังจะตกตะลึงนิดหน่อย แต่ตรงนี้มันไป: คุณรู้ไหมว่า Query Optimizer จะจัดการ MyISAM แตกต่างกันบ้างไหม?

คุณอาจพูดว่า HUH ???? อย่างไร ???

MyISAM เก็บข้อมูลใน.MYDไฟล์และดัชนีทั้งหมดใน.MYIไฟล์

แบบสอบถามเดียวกันจะสร้างแผนอธิบายที่แตกต่างกันเนื่องจากดัชนีอยู่ในไฟล์ที่แตกต่างจากข้อมูล ทำไม นี่คือเหตุผล:

  • ข้อมูลที่ต้องการ ( last_nameคอลัมน์) ได้รับคำสั่งแล้วใน.MYI
  • ในกรณีที่เลวร้ายที่สุดคุณจะมีการสแกนดัชนีแบบเต็ม
  • คุณจะเข้าถึงคอลัมน์last_nameจากดัชนีเท่านั้น
  • คุณไม่จำเป็นต้องร่อนผ่านสิ่งที่ไม่ต้องการ
  • คุณจะไม่เรียกใช้การสร้างไฟล์ชั่วคราวสำหรับการเรียงลำดับ

จะแน่ใจได้อย่างไรในเรื่องนี้? ฉันได้ทดสอบทฤษฎีการทำงานนี้เกี่ยวกับวิธีการใช้ที่เก็บข้อมูลที่แตกต่างกันจะสร้างแผนอธิบายที่แตกต่างกัน (บางครั้งดีกว่า): ดัชนีต้องครอบคลุมคอลัมน์ที่เลือกทั้งหมดเพื่อให้ใช้สำหรับ ORDER BY หรือไม่


1
-1 @Rolando คำตอบนี้ไม่แม่นยำน้อยกว่าคำตอบที่ถูกต้องของ Michael-sqlbotแต่มันก็ผิดเช่นคู่มือกล่าวว่า: "MySQL ใช้ดัชนีสำหรับการดำเนินการเหล่านี้: (... ) เพื่อเรียงลำดับหรือจัดกลุ่มตารางถ้าเรียงลำดับหรือ การจัดกลุ่มจะทำในส่วนนำหน้าซ้ายสุดของดัชนีที่ใช้งานได้ (... ) " นอกจากนี้ยังมีข้อความอื่น ๆ ของโพสต์ของคุณที่โต้แย้งได้ ฉันอยากจะแนะนำให้คุณลบคำตอบนี้หรือทำใหม่
miracle173

คำตอบนี้ไม่ถูกต้อง ดัชนีอาจยังคงถูกใช้แม้ว่าจะไม่มีส่วนคำสั่ง WHERE ถ้ามันหลีกเลี่ยงการเรียงลำดับ
oysteing

19

ที่จริงปัญหาที่นี่คือดูเหมือนว่าดัชนีคำนำหน้า ฉันไม่เห็นคำจำกัดความของตารางในคำถาม แต่sub_part= 700 คุณไม่ได้จัดทำดัชนีทั้งคอลัมน์ดังนั้นดัชนีจึงไม่สามารถใช้สำหรับการเรียงลำดับและไม่เป็นประโยชน์ในฐานะดัชนีที่ครอบคลุม มันสามารถใช้เพื่อค้นหาแถวที่ "อาจ" ตรงกับWHEREและเลเยอร์เซิร์ฟเวอร์ (เหนือเครื่องมือจัดเก็บข้อมูล) จะต้องกรองแถวที่ตรงกันเพิ่มเติม คุณต้องการ 1,000 ตัวอักษรสำหรับนามสกุลจริงหรือไม่?


อัปเดตเพื่อแสดงตัวอย่าง: ฉันมีตารางทดสอบตารางที่มี litle มากกว่า 500 แถวในนั้นแต่ละชื่อโดเมนของเว็บไซต์ในคอลัมน์domain_name VARCHAR(254) NOT NULLและไม่มีดัชนี

mysql> alter table keydemo add key(domain_name);
Query OK, 0 rows affected (0.17 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 | index | NULL          | domain_name | 764     | NULL |  541 | Using index |
+----+-------------+---------+-------+---------------+-------------+---------+------+------+-------------+
1 row in set (0.01 sec)

ดังนั้นตอนนี้ฉันจะลบดัชนีนั้นและเพียงทำดัชนี 200 ตัวอักษรแรกของ 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

โปรดทราบว่าดัชนีที่ 200 อักขระมีความยาวมากกว่าค่าที่ยาวที่สุดในคอลัมน์ ...

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

... แต่นั่นไม่ได้สร้างความแตกต่างเลย ดัชนีที่ประกาศด้วยความยาวของคำนำหน้าสามารถใช้สำหรับการค้นหาไม่ใช่สำหรับการเรียงลำดับและไม่เป็นดัชนีครอบคลุมเนื่องจากไม่ได้มีค่าคอลัมน์เต็มตามนิยาม

นอกจากนี้ยังมีการเรียกใช้แบบสอบถามข้างต้นบนตาราง InnoDB แต่การเรียกใช้บนตาราง MyISAM ให้ผลลัพธ์ที่เหมือนกัน ความแตกต่างเพียงอย่างเดียวในกรณีนี้คือการนับ InnoDB สำหรับrowsปิดเล็กน้อย (541) ในขณะที่ MyISAM แสดงจำนวนแถว (563) ที่แน่นอนซึ่งเป็นพฤติกรรมปกติ

ฉันจะยังคงยืนยันว่าคอลัมน์ last_name มีขนาดใหญ่กว่าที่ต้องการ แต่ก็ยังเป็นไปได้ที่จะจัดทำดัชนีทั้งคอลัมน์หากคุณใช้ InnoDB และใช้งาน MySQL 5.5 หรือ 5.6:

โดยค่าเริ่มต้นคีย์ดัชนีสำหรับดัชนีคอลัมน์เดียวสามารถมีได้สูงสุด 767 ไบต์ ขีดจำกัดความยาวเดียวกันนี้ใช้กับส่วนนำหน้าคีย์ดัชนีใด ๆ ดูหัวข้อ 13.1.13“ CREATE INDEXไวยากรณ์” ตัวอย่างเช่นคุณอาจถึงขีด จำกัด นี้ด้วยดัชนีคำนำหน้าคอลัมน์มากกว่า 255 อักขระในหนึ่งคอลัมน์TEXTหรือVARCHARสมมติว่าUTF-8ชุดอักขระและสูงสุด 3 ไบต์สำหรับแต่ละอักขระ เมื่อinnodb_large_prefixเปิดใช้งานตัวเลือกการกำหนดค่าขีดจำกัดความยาวนี้จะเพิ่มเป็น 3072 ไบต์สำหรับInnoDBตารางที่ใช้รูปแบบDYNAMICและCOMPRESSEDแถว

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


มุมมองที่น่าสนใจ คอลัมน์นี้เป็นvarchar(1000)แต่เกินค่าสูงสุดที่อนุญาตสำหรับดัชนีซึ่ง ~ 750
Cratylus

8
คำตอบนี้ควรเป็นคำตอบที่ยอมรับได้
ypercubeᵀᴹ

1
@ypercube คำตอบนี้แม่นยำกว่าของฉันมาก +1 สำหรับความคิดเห็นของคุณและ +1 สำหรับคำตอบนี้ ขอให้เรื่องนี้ควรได้รับการยอมรับแทนฉัน
RolandoMySQLDBA

1
@Timo นั่นเป็นคำถามที่น่าสนใจ ... ซึ่งฉันอยากจะแนะนำให้โพสต์เป็นคำถามใหม่ที่นี่อาจมีลิงก์ไปยังคำตอบสำหรับบริบท โพสต์ผลลัพธ์ที่สมบูรณ์จากEXPLAIN SELECT ...เช่นเดียวกับSHOW CREATE TABLE ...และSELECT @@VERSION;เนื่องจากการเปลี่ยนแปลงในเครื่องมือเพิ่มประสิทธิภาพข้ามรุ่นอาจมีความเกี่ยวข้อง
Michael - sqlbot

1
โดยตอนนี้ฉันสามารถรายงานได้ว่า (อย่างน้อยสำหรับ 5.7) ดัชนีคำนำหน้าไม่ได้ช่วยในการทำดัชนีโมฆะตามที่ฉันขอในความคิดเห็นของฉันด้านบน
Timo

2

ฉันได้รับคำตอบเกี่ยวกับเพราะความคิดเห็นจะไม่สนับสนุนการจัดรูปแบบและ RolandoMySQL DBA พูดคุยเกี่ยวกับ gen_clust_index และ innodb และนี่เป็นสิ่งสำคัญมากในตารางที่อิงจาก Innodb สิ่งนี้ไปไกลกว่าความรู้ DBA ปกติเพราะคุณต้องสามารถวิเคราะห์รหัส C ได้

คุณควรสร้างคีย์หลักหรือคีย์ที่ไม่ซ้ำกันเสมอหากคุณใช้ Innodb หากคุณไม่ใช้ Innodb จะใช้ ROW_ID ที่สร้างขึ้นเองซึ่งอาจเป็นอันตรายมากกว่าความดี

ฉันจะพยายามอธิบายให้ง่ายเพราะการพิสูจน์จะขึ้นอยู่กับรหัส 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);
}

ปัญหาแรก

mutex_enter ((dict_sys-> mutex));

บรรทัดนี้ทำให้แน่ใจว่ามีเพียงหนึ่งเธรดเท่านั้นที่สามารถเข้าถึง dict_sys-> mutex ได้ในเวลาเดียวกัน จะเกิดอะไรขึ้นถ้าค่านั้นถูก mutexed แล้ว ... ใช่แล้วเธรดต้องรอดังนั้นคุณจะได้รับคุณลักษณะแบบสุ่มที่ดีเช่นการล็อกเธรดหรือถ้าคุณมีตารางเพิ่มเติมโดยไม่มีคีย์หลักหรือคีย์ UNIQUE ของคุณเองคุณจะมีฟีเจอร์ที่ดี Innodb 'การล็อกตาราง ' ไม่ใช่เหตุผลที่ MyISAM ถูกแทนที่โดย InnoDB เนื่องจากคุณลักษณะที่ดีที่เรียกว่าการล็อกตามแถว / บันทึก

ปัญหาที่สอง

(0 == (id% DICT_HDR_ROW_ID_WRITE_MARGIN))

การคำนวณแบบโมดูโล (%) นั้นช้าไม่ดีถ้าคุณใส่แบตช์เพราะต้องคำนวณใหม่ทุกครั้ง ... และเพราะ DICT_HDR_ROW_ID_WRITE_MARGIN (ค่า 256) เป็นพลังของสองสิ่งนี้อาจทำให้เร็วขึ้นมาก ..

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

หมายเหตุด้านข้างหากคอมไพเลอร์ C ได้รับการกำหนดค่าให้เหมาะสมและเป็นตัวเพิ่มประสิทธิภาพที่ดีตัวเพิ่มประสิทธิภาพ C จะแก้ไขรหัส "หนัก" เป็นรุ่นที่เบากว่า

คำขวัญของเรื่องราวสร้างคีย์หลักของคุณเองเสมอหรือให้แน่ใจว่าคุณมีดัชนีที่ไม่ซ้ำกันเมื่อคุณสร้างตารางตั้งแต่เริ่มต้น


เพิ่มการจำลองแบบตามแถวและความจริงที่ว่า ID แถวไม่สอดคล้องกันในเซิร์ฟเวอร์และจุดของเรย์มอนด์เกี่ยวกับการสร้างคีย์หลักเสมอสำคัญยิ่งขึ้น

โปรดอย่าแนะนำว่าUNIQUEเพียงพอ - นอกจากนี้ยังจำเป็นต้องรวมเฉพาะคอลัมน์ที่ไม่ใช่ค่า NULL เพื่อให้ดัชนีเฉพาะเลื่อนระดับเป็น PK
Rick James

"การคำนวณแบบโมดูโล (%) ช้า" - ที่สำคัญกว่าคือเปอร์เซ็นต์ของเวลาที่INSERTใช้ในฟังก์ชั่นนี้ ฉันสงสัยว่าไม่มีนัยสำคัญ คมชัดความพยายามที่จะคอลัมน์จอบรอบจะดำเนินการ BTree รวมทั้งเป็นครั้งคราวบล็อกแยก mutexes ต่างๆใน buffer_pool สิ่งที่เปลี่ยนแปลงบัฟเฟอร์ ฯลฯ
ริกเจมส์

True @RickJames ค่าโสหุ้ยอาจน้อยมาก แต่ตัวเลขเล็ก ๆ จำนวนมากก็เพิ่มขึ้น (ยังคงเป็นการเพิ่มประสิทธิภาพขนาดเล็ก) .. นอกจากปัญหาแรกคือปัญหามากที่สุดบางอย่าง
Raymond Nijland
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.