## 索引 索引是关系型数æ®åº“ä¸ç”¨æ¥æå‡æŸ¥è¯¢æ€§èƒ½æœ€ä¸ºé‡è¦çš„æ‰‹æ®µã€‚关系型数æ®åº“ä¸çš„索引就åƒä¸€æœ¬ä¹¦çš„目录,我们å¯ä»¥æƒ³è±¡ä¸€ä¸‹ï¼Œå¦‚æžœè¦ä»Žä¸€æœ¬ä¹¦ä¸æ‰¾å‡ºæŸä¸ªçŸ¥è¯†ç‚¹ï¼Œä½†æ˜¯è¿™æœ¬ä¹¦æ²¡æœ‰ç›®å½•ï¼Œè¿™å°†æ˜¯ä¸€ä»¶å¤šä¹ˆå¯æ€•çš„äº‹æƒ…ï¼æˆ‘们估计得一篇一篇的翻下去,æ‰èƒ½ç¡®å®šè¿™ä¸ªçŸ¥è¯†ç‚¹åˆ°åº•在什么ä½ç½®ã€‚创建索引虽然会带æ¥å˜å‚¨ç©ºé—´ä¸Šçš„开销,就åƒä¸€æœ¬ä¹¦çš„目录会å ç”¨ä¸€éƒ¨åˆ†ç¯‡å¹…ä¸€æ ·ï¼Œä½†æ˜¯åœ¨ç‰ºç‰²ç©ºé—´åŽæ¢æ¥çš„æŸ¥è¯¢æ—¶é—´çš„å‡å°‘也是éžå¸¸æ˜¾è‘—的。 MySQL æ•°æ®åº“䏿‰€æœ‰æ•°æ®ç±»åž‹çš„列都å¯ä»¥è¢«ç´¢å¼•。对于MySQL 8.0 版本的 InnoDB å˜å‚¨å¼•擎æ¥è¯´ï¼Œå®ƒæ”¯æŒä¸‰ç§ç±»åž‹çš„索引,分别是 B+ æ ‘ç´¢å¼•ã€å…¨æ–‡ç´¢å¼•å’Œ R æ ‘ç´¢å¼•ã€‚è¿™é‡Œï¼Œæˆ‘ä»¬åªä»‹ç»ä½¿ç”¨å¾—最为广泛的 B+ æ ‘ç´¢å¼•ã€‚ä½¿ç”¨ B+ æ ‘çš„åŽŸå› éžå¸¸ç®€å•ï¼Œå› ä¸ºå®ƒæ˜¯ç›®å‰åœ¨åŸºäºŽç£ç›˜è¿›è¡Œæµ·é‡æ•°æ®å˜å‚¨å’ŒæŽ’åºä¸Šæœ€æœ‰æ•ˆçŽ‡çš„æ•°æ®ç»“构。B+ æ ‘æ˜¯ä¸€æ£µ[å¹³è¡¡æ ‘](https://zh.wikipedia.org/zh-cn/%E5%B9%B3%E8%A1%A1%E6%A0%91)ï¼Œæ ‘çš„é«˜åº¦é€šå¸¸ä¸º3或4,但是å´å¯ä»¥ä¿å˜ä»Žç™¾ä¸‡çº§åˆ°å亿级的数æ®ï¼Œè€Œä»Žè¿™äº›æ•°æ®é‡Œé¢æŸ¥è¯¢ä¸€æ¡æ•°æ®ï¼Œåªéœ€è¦3次或4次 I/O æ“作。 B+ æ ‘ç”±æ ¹èŠ‚ç‚¹ã€ä¸é—´èŠ‚ç‚¹å’Œå¶å节点构æˆï¼Œå…¶ä¸å¶å节点用æ¥ä¿å˜æŽ’åºåŽçš„æ•°æ®ã€‚由于记录在索引上是排åºè¿‡çš„ï¼Œå› æ¤åœ¨ä¸€ä¸ªå¶åèŠ‚ç‚¹å†…æŸ¥æ‰¾æ•°æ®æ—¶å¯ä»¥ä½¿ç”¨äºŒåˆ†æŸ¥æ‰¾ï¼Œè¿™ç§æŸ¥æ‰¾æ–¹å¼æ•ˆçއéžå¸¸çš„高。当数æ®å¾ˆå°‘的时候,B+ æ ‘åªæœ‰ä¸€ä¸ªæ ¹èŠ‚ç‚¹ï¼Œæ•°æ®ä¹Ÿå°±ä¿å˜åœ¨æ ¹èŠ‚ç‚¹ä¸Šã€‚éšç€è®°å½•è¶Šæ¥è¶Šå¤šï¼ŒB+ æ ‘ä¼šå‘ç”Ÿåˆ†è£‚ï¼Œæ ¹èŠ‚ç‚¹ä¸å†ä¿å˜æ•°æ®ï¼Œè€Œæ˜¯æä¾›äº†è®¿é—®ä¸‹ä¸€å±‚节点的指针,帮助快速确定数æ®åœ¨å“ªä¸ªå¶å节点上。 在创建二维表时,我们通常都会为表指定主键列,主键列上默认会创建索引,而对于 MySQL InnoDB å˜å‚¨å¼•擎æ¥è¯´ï¼Œå› ä¸ºå®ƒä½¿ç”¨çš„æ˜¯ç´¢å¼•ç»„ç»‡è¡¨è¿™ç§æ•°æ®å˜å‚¨ç»“æž„ï¼Œæ‰€ä»¥ä¸»é”®ä¸Šçš„ç´¢å¼•å°±æ˜¯æ•´å¼ è¡¨çš„æ•°æ®ï¼Œè€Œè¿™ç§ç´¢å¼•我们也将其称之为**èšé›†ç´¢å¼•**(clustered indexï¼‰ã€‚å¾ˆæ˜¾ç„¶ï¼Œä¸€å¼ è¡¨åªèƒ½æœ‰ä¸€ä¸ªèšé›†ç´¢å¼•,å¦åˆ™è¡¨çš„æ•°æ®å²‚䏿˜¯è¦ä¿å˜å¤šæ¬¡ã€‚我们自己创建的索引都是二级索引(secondary index),更常è§çš„嫿³•是**éžèšé›†ç´¢å¼•**(non-clustered index)。通过我们自定义的éžèšé›†ç´¢å¼•åªèƒ½å®šä½è®°å½•çš„ä¸»é”®ï¼Œåœ¨èŽ·å–æ•°æ®æ—¶å¯èƒ½éœ€è¦å†é€šè¿‡ä¸»é”®ä¸Šçš„èšé›†ç´¢å¼•进行查询,这ç§çŽ°è±¡ç§°ä¸ºâ€œå›žè¡¨â€ï¼Œå› æ¤é€šè¿‡éžèšé›†ç´¢å¼•检索数æ®é€šå¸¸æ¯”使用èšé›†ç´¢å¼•检索数æ®è¦æ…¢ã€‚ æŽ¥ä¸‹æ¥æˆ‘们通过一个简å•çš„ä¾‹åæ¥è¯´æ˜Žç´¢å¼•çš„æ„ä¹‰ï¼Œæ¯”å¦‚æˆ‘ä»¬è¦æ ¹æ®å¦ç”Ÿçš„å§“åæ¥æŸ¥æ‰¾å¦ç”Ÿï¼Œè¿™ä¸ªåœºæ™¯åœ¨å®žé™…å¼€å‘ä¸åº”该ç»å¸¸é‡åˆ°ï¼Œå°±è·Ÿé€šè¿‡å•†å“åç§°æŸ¥æ‰¾å•†å“æ˜¯ä¸€ä¸ªé“ç†ã€‚我们å¯ä»¥ä½¿ç”¨ MySQL çš„`explain`关键嗿¥æŸ¥çœ‹ SQL 的执行计划(数æ®åº“执行 SQL è¯å¥çš„具体æ¥éª¤ï¼‰ã€‚ ```SQL explain select * from tb_student where stuname='林震å—'\G ``` ``` *************************** 1. row *************************** id: 1 select_type: SIMPLE table: tb_student partitions: NULL type: ALL possible_keys: NULL key: NULL key_len: NULL ref: NULL rows: 11 filtered: 10.00 Extra: Using where 1 row in set, 1 warning (0.00 sec) ``` 在上é¢çš„ SQL 执行计划ä¸ï¼Œæœ‰å‡ 项值得我们关注: 1. `select_type`:查询的类型。 - `SIMPLE`ï¼šç®€å• SELECT,ä¸éœ€è¦ä½¿ç”¨ UNION æ“ä½œæˆ–åæŸ¥è¯¢ã€‚ - `PRIMARY`:如果查询包å«å查询,最外层的 SELECT è¢«æ ‡è®°ä¸º PRIMARY。 - `UNION`:UNION æ“作ä¸ç¬¬äºŒä¸ªæˆ–åŽé¢çš„ SELECT è¯å¥ã€‚ - `SUBQUERY`ï¼šåæŸ¥è¯¢ä¸çš„第一个 SELECT。 - `DERIVED`:派生表的 SELECT åæŸ¥è¯¢ã€‚ 2. `table`:查询对应的表。 3. `type`:MySQL åœ¨è¡¨ä¸æ‰¾åˆ°æ»¡è¶³æ¡ä»¶çš„行的方å¼ï¼Œä¹Ÿç§°ä¸ºè®¿é—®ç±»åž‹ï¼ŒåŒ…括:`ALL`(全表扫æï¼‰ã€`index`(索引全扫æï¼ŒåªéåŽ†ç´¢å¼•æ ‘ï¼‰ã€`range`(索引范围扫æï¼‰ã€`ref`(éžå”¯ä¸€ç´¢å¼•扫æï¼‰ã€`eq_ref`(唯一索引扫æï¼‰ã€`const` / `system`(常é‡çº§æŸ¥è¯¢ï¼‰ã€`NULL`(ä¸éœ€è¦è®¿é—®è¡¨æˆ–索引)。在所有的访问类型ä¸ï¼Œå¾ˆæ˜¾ç„¶ ALL æ˜¯æ€§èƒ½æœ€å·®çš„ï¼Œå®ƒä»£è¡¨çš„å…¨è¡¨æ‰«ææ˜¯æŒ‡è¦æ‰«æè¡¨ä¸çš„æ¯ä¸€è¡Œæ‰èƒ½æ‰¾åˆ°åŒ¹é…的行。 4. `possible_keys`:MySQL å¯ä»¥é€‰æ‹©çš„索引,但是**有å¯èƒ½ä¸ä¼šä½¿ç”¨**。 5. `key`:MySQL 真æ£ä½¿ç”¨çš„索引,如果为`NULL`就表示没有使用索引。 6. `key_len`:使用的索引的长度,在ä¸å½±å“查询的情况下肯定是长度越çŸè¶Šå¥½ã€‚ 7. `rows`ï¼šæ‰§è¡ŒæŸ¥è¯¢éœ€è¦æ‰«æçš„行数,这是一个**预估值**。 8. `extra`:关于查询é¢å¤–的信æ¯ã€‚ - `Using filesort`:MySQL æ— æ³•åˆ©ç”¨ç´¢å¼•å®ŒæˆæŽ’åºæ“作。 - `Using index`:åªä½¿ç”¨ç´¢å¼•的信æ¯è€Œä¸éœ€è¦è¿›ä¸€æ¥æŸ¥è¡¨æ¥èŽ·å–æ›´å¤šçš„ä¿¡æ¯ã€‚ - `Using temporary`:MySQL 需è¦ä½¿ç”¨ä¸´æ—¶è¡¨æ¥å˜å‚¨ç»“果集,常用于分组和排åºã€‚ - `Impossible where`:`where`åå¥ä¼šå¯¼è‡´æ²¡æœ‰ç¬¦åˆæ¡ä»¶çš„行。 - `Distinct`:MySQL å‘现第一个匹é…行åŽï¼Œåœæ¢ä¸ºå½“å‰çš„è¡Œç»„åˆæœç´¢æ›´å¤šçš„行。 - `Using where`:查询的列未被索引覆盖,ç›é€‰æ¡ä»¶å¹¶ä¸æ˜¯ç´¢å¼•çš„å‰å¯¼åˆ—。 从上é¢çš„æ‰§è¡Œè®¡åˆ’å¯ä»¥çœ‹å‡ºï¼Œå½“我们通过å¦ç”Ÿåå—æŸ¥è¯¢å¦ç”Ÿæ—¶å®žé™…上是进行了全表扫æï¼Œä¸è¨€è€Œå–»è¿™ä¸ªæŸ¥è¯¢æ€§èƒ½è‚¯å®šæ˜¯éžå¸¸ç³Ÿç³•的,尤其是在表ä¸çš„行很多的时候。如果我们需è¦ç»å¸¸é€šè¿‡å¦ç”Ÿå§“åæ¥æŸ¥è¯¢å¦ç”Ÿï¼Œé‚£ä¹ˆå°±åº”该在å¦ç”Ÿå§“å对应的列上创建索引,通过索引æ¥åŠ é€ŸæŸ¥è¯¢ã€‚ ```SQL create index idx_student_name on tb_student(stuname); ``` 冿¬¡æŸ¥çœ‹åˆšæ‰çš„ SQL 对应的执行计划。 ```SQL explain select * from tb_student where stuname='林震å—'\G ``` ``` *************************** 1. row *************************** id: 1 select_type: SIMPLE table: tb_student partitions: NULL type: ref possible_keys: idx_student_name key: idx_student_name key_len: 62 ref: const rows: 1 filtered: 100.00 Extra: NULL 1 row in set, 1 warning (0.00 sec) ``` å¯ä»¥æ³¨æ„到,在对å¦ç”Ÿå§“å创建索引åŽï¼Œåˆšæ‰çš„æŸ¥è¯¢å·²ç»ä¸æ˜¯å…¨è¡¨æ‰«æè€Œæ˜¯åŸºäºŽç´¢å¼•的查询,而且扫æçš„è¡Œåªæœ‰å”¯ä¸€çš„一行,这显然大大的æå‡äº†æŸ¥è¯¢çš„æ€§èƒ½ã€‚MySQL ä¸è¿˜å…许创建å‰ç¼€ç´¢å¼•,å³å¯¹ç´¢å¼•å—æ®µçš„å‰N个å—ç¬¦åˆ›å»ºç´¢å¼•ï¼Œè¿™æ ·çš„è¯å¯ä»¥å‡å°‘索引å 用的空间(但节çœäº†ç©ºé—´å¾ˆæœ‰å¯èƒ½ä¼šæµªè´¹æ—¶é—´ï¼Œ**时间和空间是ä¸å¯è°ƒå’Œçš„矛盾**),如下所示。 ```SQL create index idx_student_name_1 on tb_student(stuname(1)); ``` 上é¢çš„ç´¢å¼•ç›¸å½“äºŽæ˜¯æ ¹æ®å¦ç”Ÿå§“åçš„ç¬¬ä¸€ä¸ªå—æ¥åˆ›å»ºçš„索引,我们å†çœ‹çœ‹ SQL 执行计划。 ```SQL explain select * from tb_student where stuname='林震å—'\G ``` ``` *************************** 1. row *************************** id: 1 select_type: SIMPLE table: tb_student partitions: NULL type: ref possible_keys: idx_student_name key: idx_student_name key_len: 5 ref: const rows: 2 filtered: 100.00 Extra: Using where 1 row in set, 1 warning (0.00 sec) ``` ä¸çŸ¥é“å¤§å®¶æ˜¯å¦æ³¨æ„到,这一次扫æçš„è¡Œå˜æˆäº†2è¡Œï¼Œå› ä¸ºå¦ç”Ÿè¡¨ä¸æœ‰ä¸¤ä¸ªå§““林â€çš„å¦ç”Ÿï¼Œæˆ‘们åªç”¨å§“å的第一个å—作为索引的è¯ï¼Œåœ¨æŸ¥è¯¢æ—¶é€šè¿‡ç´¢å¼•就会找到这两行。 如果è¦åˆ 除索引,å¯ä»¥ä½¿ç”¨ä¸‹é¢çš„SQL。 ```SQL alter table tb_student drop index idx_student_name; ``` 或者 ```SQL drop index idx_student_name on tb_student; ``` 在创建索引时,我们还å¯ä»¥ä½¿ç”¨å¤åˆç´¢å¼•ã€å‡½æ•°ç´¢å¼•(MySQL 5.7 开始支æŒï¼‰ï¼Œç”¨å¥½å¤åˆç´¢å¼•实现**索引覆盖**å¯ä»¥å‡å°‘ä¸å¿…è¦çš„æŽ’åºå’Œå›žè¡¨æ“ä½œï¼Œè¿™æ ·å°±ä¼šè®©æŸ¥è¯¢çš„æ€§èƒ½æˆå€çš„æå‡ï¼Œæœ‰å…´è¶£çš„读者å¯ä»¥è‡ªè¡Œç ”究。 我们简å•的为大家总结一下索引的设计原则: 1. **最适åˆ**索引的列是出现在**WHEREåå¥**和连接åå¥ä¸çš„列。 2. 索引列的基数越大(å–值多ã€é‡å¤å€¼å°‘),索引的效果就越好。 3. 使用**å‰ç¼€ç´¢å¼•**å¯ä»¥å‡å°‘索引å 用的空间,内å˜ä¸å¯ä»¥ç¼“å˜æ›´å¤šçš„索引。 4. **ç´¢å¼•ä¸æ˜¯è¶Šå¤šè¶Šå¥½**ï¼Œè™½ç„¶ç´¢å¼•åŠ é€Ÿäº†è¯»æ“作(查询),但是写æ“作(增ã€åˆ ã€æ”¹ï¼‰éƒ½ä¼šå˜å¾—æ›´æ…¢ï¼Œå› ä¸ºæ•°æ®çš„å˜åŒ–会导致索引的更新,就如åŒä¹¦ç±ç« èŠ‚çš„å¢žåˆ éœ€è¦æ›´æ–°ç›®å½•ä¸€æ ·ã€‚ 5. 使用 InnoDB å˜å‚¨å¼•擎时,表的普通索引都会ä¿å˜ä¸»é”®çš„值,所以**主键è¦å°½å¯èƒ½é€‰æ‹©è¾ƒçŸçš„æ•°æ®ç±»åž‹**ï¼Œè¿™æ ·å¯ä»¥æœ‰æ•ˆçš„å‡å°‘索引å 用的空间,æå‡ç´¢å¼•çš„ç¼“å˜æ•ˆæžœã€‚ 最åŽï¼Œè¿˜æœ‰ä¸€ç‚¹éœ€è¦è¯´æ˜Žï¼ŒInnoDB 使用的 B-tree 索引,数值类型的列除了ç‰å€¼åˆ¤æ–时索引会生效之外,使用`>`ã€`<`ã€`>=`ã€`<=`ã€`BETWEEN...AND... `ã€`<>`时,索引ä»ç„¶ç”Ÿæ•ˆï¼›å¯¹äºŽå—符串类型的列,如果使用ä¸ä»¥é€šé…符开头的模糊查询,索引也是起作用的,但是其他的情况会导致索引失效,这就æ„味ç€å¾ˆæœ‰å¯èƒ½ä¼šåšå…¨è¡¨æŸ¥è¯¢ã€‚