จำเป็นหรือไม่ที่ทุกคอลัมน์ที่เลือกต้องถูกจัดทำดัชนีเพื่อให้ MySQL เลือกใช้ดัชนี?
นี่เป็นคำถามที่โหลดเพราะมีปัจจัยที่กำหนดว่าดัชนีคุ้มค่าหรือไม่
ปัจจัย # 1
สำหรับดัชนีใด ๆ ประชากรหลักคืออะไร กล่าวอีกนัยหนึ่ง cardinality (จำนวนที่แตกต่าง) ของสิ่งอันดับทั้งหมดที่บันทึกในดัชนีคืออะไร?
ปัจจัย # 2
คุณใช้เครื่องมือเก็บข้อมูลอะไร คอลัมน์ที่ต้องการทั้งหมดสามารถเข้าถึงได้จากดัชนีหรือไม่
อะไรต่อไป ???
ลองมาตัวอย่างง่าย ๆ : ตารางที่มีสองค่า (ชายและหญิง)
ให้สร้างตารางดังกล่าวพร้อมกับทดสอบการใช้ดัชนี
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';
ทดสอบ 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>
ทดสอบ 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>
การวิเคราะห์สำหรับ InnoDB
เมื่อข้อมูลถูกโหลดเป็น InnoDB โปรดทราบว่าทั้งสี่EXPLAIN
แผนใช้gender
ดัชนี ที่สามและสี่EXPLAIN
แผนการใช้ดัชนีแม้ว่าข้อมูลที่ร้องขอได้gender
id
ทำไม? เนื่องจากid
อยู่ในPRIMARY KEY
และดัชนีรองทั้งหมดมีตัวชี้อ้างอิงกลับไปที่PRIMARY KEY
(ผ่านgen_clust_index )
การวิเคราะห์สำหรับ MyISAM
เมื่อข้อมูลถูกโหลดเป็น MyISAM โปรดทราบว่าสามEXPLAIN
แผนแรกใช้gender
ดัชนี ในEXPLAIN
แผนสี่เครื่องมือเพิ่มประสิทธิภาพข้อความค้นหาตัดสินใจไม่ใช้ดัชนีเลย มันเลือกที่จะสแกนแบบเต็มตารางแทน ทำไม?
โดยไม่คำนึงถึง DBMS เครื่องมือเพิ่มประสิทธิภาพการสืบค้นจะดำเนินการด้วยกฎง่ายๆ: หากดัชนีถูกคัดกรองในฐานะผู้สมัครที่จะใช้สำหรับการดำเนินการค้นหาและเครื่องมือเพิ่มประสิทธิภาพแบบสอบถามจะคำนวณว่าต้องค้นหามากกว่า 5% ของจำนวนทั้งหมด แถวในตาราง:
- การสแกนดัชนีแบบเต็มจะทำถ้าคอลัมน์ที่จำเป็นทั้งหมดสำหรับการดึงข้อมูลอยู่ในดัชนีที่เลือก
- สแกนตารางเต็มมิฉะนั้น
สรุปผลการศึกษา
หากคุณไม่มีดัชนีการครอบคลุมที่เหมาะสมหรือหากประชากรหลักสำหรับสิ่งอันดับใด ๆ มีค่ามากกว่า 5% ของตารางต้องมีสิ่งต่าง ๆ หกอย่าง:
- มาตระหนักว่าคุณต้องโปรไฟล์แบบสอบถาม
- ค้นหาทั้งหมด
WHERE
, GROUP BY
และการสั่งซื้อ BY` คำสั่งจากแบบสอบถามเหล่านั้น
- กำหนดดัชนีในลำดับนี้
WHERE
คอลัมน์มาตราที่มีค่าคงที่
GROUP BY
คอลัมน์
ORDER BY
คอลัมน์
- หลีกเลี่ยงการสแกนเต็มตาราง (การค้นหาที่ไม่มี
WHERE
ประโยคที่สมเหตุสมผล)
- หลีกเลี่ยง Bad Key Populations (หรืออย่างน้อยแคชประชากร Bad Key เหล่านั้น)
- ตัดสินใจเลือก MySQL Storage Engine ที่ดีที่สุด ( InnoDBหรือMyISAM ) สำหรับ Tables
ฉันได้เขียนเกี่ยวกับกฎ 5% ของหัวแม่มือในอดีตที่ผ่านมา:
อัพเดท 2012-11-14 13:05 EDT
ผมเอากลับมาดูที่คำถามของคุณและในการโพสต์ SO เดิม จากนั้นฉันก็คิดถึงสิ่งที่Analysis for InnoDB
ฉันพูดถึงก่อนหน้านี้ มันตรงกับperson
ตาราง ทำไม?
สำหรับทั้งโต๊ะmf
และperson
- Storage Engine คือ InnoDB
- คีย์หลักคือ
id
- การเข้าถึงตารางโดยดัชนีรอง
- ถ้าตารางคือ MyISAM เราจะเห็น
EXPLAIN
แผนแตกต่างอย่างสิ้นเชิง
ตอนนี้มองไปที่การสอบถามจากคำถาม SO select * from person order by age\G
นี้: เนื่องจากไม่มีWHERE
ข้อคุณอย่างชัดเจนเรียกร้องตารางการสแกนเต็มรูปแบบ การเรียงลำดับเริ่มต้นของตารางจะเป็นโดยid
(คีย์หลัก) เพราะ auto_increment และของgen_clust_index (aka ดัชนีคลัสเตอร์) ได้รับคำสั่งจาก ROWID เมื่อคุณเรียงลำดับตามดัชนีโปรดจำไว้ว่าดัชนีรอง InnoDB มีการแนบแถวเข้ากับรายการดัชนีแต่ละรายการ สิ่งนี้สร้างความต้องการภายในสำหรับการเข้าถึงแถวเต็มทุกครั้ง
การตั้งค่าORDER BY
บนตาราง InnoDB อาจเป็นงานที่ค่อนข้างยุ่งยากหากคุณไม่สนใจข้อเท็จจริงเหล่านี้เกี่ยวกับวิธีการจัดเรียงดัชนี InnoDB
กลับไปที่คำสั่ง SO นั้นเนื่องจากคุณต้องการสแกนแบบเต็มตาราง IMHO the MySQL Query Optimizer ทำสิ่งที่ถูกต้อง (หรืออย่างน้อยเลือกเส้นทางที่มีความต้านทานน้อยที่สุด) เมื่อพูดถึง InnoDB และการสืบค้น SO มันง่ายกว่ามากที่จะทำการสแกนตารางแบบเต็มรูปแบบและบางส่วนfilesort
แทนที่จะทำการสแกนดัชนีแบบเต็มและการค้นหาแถวผ่าน gen_clust_index สำหรับรายการดัชนีรองแต่ละรายการ
ฉันไม่ได้เป็นผู้สนับสนุนการใช้ดัชนีคำแนะนำเพราะมันละเว้นแผนอธิบาย อย่างไรก็ตามหากคุณรู้จักข้อมูลของคุณดีกว่า InnoDB จริงๆคุณจะต้องหันไปใช้ดัชนีคำแนะนำโดยเฉพาะอย่างยิ่งกับข้อความค้นหาที่ไม่มีWHERE
ส่วนคำสั่ง
อัพเดท 2012-11-14 14:21 EDT
ตามหนังสือเข้าใจ MySQL ภายใน
หน้า 202 ย่อหน้า 7 พูดว่าต่อไปนี้:
ข้อมูลถูกเก็บไว้ในโครงสร้างพิเศษที่เรียกว่าดัชนีคลัสเตอร์ซึ่งเป็นต้นไม้ B ที่มีคีย์หลักทำหน้าที่เป็นค่าคีย์และบันทึกจริง (แทนที่จะเป็นตัวชี้) ในส่วนข้อมูล ดังนั้นแต่ละตาราง InnoDB จะต้องมีคีย์หลัก หากไม่มีการระบุจะไม่มีการเพิ่มคอลัมน์ ID แถวพิเศษที่ผู้ใช้ไม่สามารถมองเห็นได้เพื่อทำหน้าที่เป็นคีย์หลัก คีย์รองจะเก็บค่าของคีย์หลักที่ระบุระเบียน รหัส B ต้นไม้สามารถพบได้ในinnobase / BTR / btr0btr.c
นี่คือเหตุผลที่ผมกล่าวก่อนหน้านี้: มันอยู่ไกลง่ายต่อการดำเนินการตารางการสแกนเต็มรูปแบบแล้ว filesort บางมากกว่าการทำดัชนีสแกนเต็มรูปแบบและการค้นหาแถวผ่าน gen_clust_index สำหรับรายการดัชนีแต่ละรอง InnoDB จะไปทำดัชนีการค้นหาคู่ทุกครั้ง นั่นฟังดูโหดร้าย แต่นั่นเป็นเพียงข้อเท็จจริง อีกครั้งคำนึงถึงการขาดWHERE
ประโยค ในตัวมันเองนั้นเป็นคำใบ้ของ MySQL Query Optimizer ที่จะทำการสแกนเต็มตาราง
FOR ORDER BY
(ซึ่งเป็นกรณีเฉพาะในคำถามนี้) คำถามนี้ระบุว่าในกรณีนี้เครื่องมือจัดเก็บข้อมูลเป็นInnoDB
(และคำถาม SO ดั้งเดิมแสดงให้เห็นว่าแถว 10k มีการกระจายอย่างเท่าเทียมกันใน 8 รายการความเป็นเชิงการนับไม่ควรเป็นปัญหาเช่นกัน) น่าเศร้าที่ฉันไม่คิดว่านี่เป็นคำตอบของคำถาม