MySQL之索引系列(二)

有了索引必定会增长查询速度么,如何利用好索引呢html

如今有如下表结构及数据,执行 select * from T between 3 and 5,须要执行几回树的搜索,会扫描多少行?
图1 表的结构:
mysql

表结构

图2 表中数据:
sql

表中数据

图3 InnoDB的索引组织结构:
数据库

InnoDB的索引组织结构

由图1及图3可知,ID为主键索引,k为非主键索引,K的叶子节点值为主键ID。
如下是语句的执行过程:性能优化

  1. 在k索引树查找k=3的记录,获得ID=300;
  2. 根据ID=300查找对应的R3;
  3. 在k索引树查找下一个k=5的记录,获得ID=500;
  4. 根据ID=500找到R4;
  5. 在k索引树找下一个k=6,不知足,退出。

从非主键索引回到主键索引树搜索的过程,称之为回表。以上过程当中,读了k索引树的三条记录(步骤一、三、5),回表了两次(步骤二、4)。 在引擎内部经过索引k上实际上是读了三个记录:k=三、四、5,可是对于MySQL的server层来讲,他在找引擎拿到了两条记录(具体查找过程当中server与引擎的执行过程见一条SQL查询语句的执行过程 ),所以MySQL认为扫描行数为2。具体如何查看扫描行数后面的会介绍。
了解了回表的过程,下面来看如何优化索引来避免回表数据库设计

覆盖索引

上文执行的SQL语句若是是 select ID from T where k between 3 and 5,只是请求ID值,那么这个值已经存在于k索引树上,已经能够直接提供结果,不须要回表。也就是说,新的SQL中索引k已经 "覆盖了" 语句的查询需求,以上称之为覆盖索引。
索引覆盖能够减小树的搜索次数,以此来提高查询性能,因此使用覆盖索引是常见的性能优化手段post

基于覆盖索引,来看一个新的例子:
有一个用户表,定义以下:性能

CREATE TABLE `tuser` (
`id` INT ( 11 ) NOT NULL,
`id_card` VARCHAR ( 32 ) DEFAULT NULL,
`name` VARCHAR ( 32 ) DEFAULT NULL,
`age` INT ( 11 ) DEFAULT NULL,
`ismale` TINYINT ( 1 ) DEFAULT NULL,
PRIMARY KEY ( `id` ),
KEY `id_card` ( `id_card` ),
KEY `name_age` ( `name`, `age` ) 
) ENGINE = INNODB
复制代码

已知经过身份证号是惟一标识,为了知足根据身份证号查到一个用户的全部信息,只须要对身份证号创建索引就够了。在创建一个(id_Card,name)的联合索引,究竟有没有必要?学习

若是如今有这样一个高频请求:根据身份证号找到用户的姓名,那么这个联合索引的意义就体现出来了,这个高频的请求可以用到覆盖索引,由于在(id_Card,name)这个联合索引树上直接能根据身份证号查到姓名,不用在拿到主键ID后回表查找整行记录,减小语句的执行时间。 覆盖索引的目的就是尽可能可以一次查到须要数据,避免有回表过程。优化

最左前缀原则

按照刚刚的需求,为了根据身份证号查到姓名,须要建立(id_card,name)联合索引,若是如今新的需求须要根据身份证号查到家庭住址,总不能再添加一个(id_card,addr)联合索引,为了每一种查询都设计一个索引,显然是不现实的。 这时候应该怎么作呢?
结论:B+树这种索引结构,能够利用索引的"最左前缀"来定位记录

以联合索引("name",age)为例
图4 (name,age)索引示意图:

图 4 (name,age)索引示意图

由上图可知索引项是按照索引定义的字段顺序排序的。

若是要查询 "name = '张四'" 的全部结果时,会快速定位到D4,而后向后遍历到D5结束,返回结果。
若是想查找全部姓张的结果,则条件为"where name like '张%'" ,此时仍是能够用上这个联合索引,定位到D3,而后向后遍历,直到D5结束,返回结果。

由上面两个查询可知,只要知足最左边的N个字段或者M个字符,就能够用这个索引来查询。

以上结论又引起一个新的问题:考虑到最左前缀原则,创建联合索引时,内部的字段顺序该如何安排?

  1. 若是已经存在(a,b)联合索引,则不须要对a字段创建索引。若是可以调整联合索引的顺序,来减小一个索引,name这个顺序应该被优先考虑,以此来提升索引的复用能力。 回到本节开头的问题:没有必要为(id_card,addr)建一条索引。能够复用已经存在高频使用的(id_card,name)联合索引,在根据id_card查找地址时,也可以根据最左前缀原则匹配到(id_card,name)索引,以此来增长查找速度。

  2. 对于id_card和name字段,能够创建三个索引搜索树: 对id_card和name分别创建索引、对(id_card,name)创建联合索引。根据上一条原则,能够经过调整联合索引的字段顺序来减小一个索引,即保留"(id_card,name)和name索引"或者"保留(name,id_card)和id_card索引"。这时须要考虑空间的占用状况了,显然id_card要比name字段大,那就尽可能复用id_card,建议使用(id_card,name)联合索引和name单字节索引。

索引下推

图 4 (name,age)索引示意图
仍是以上图为例,分析如下SQL执行过程

select * from tuser where name like '张%' and age = 23;    
复制代码
  • 在MySQL5.6以前,经过最左前缀原则定位到D3,而后从D3开始一个一个回表,到主键索引树上找到对应行对比age字段的值,直到D5结束,不能匹配到'张%'后退出查询,以上共回表3次。虽然比遍历全表快,可是仍是作了屡次无用的回表操做。

  • 在MySQL5.6及之后,引入了索引下推优化(index condition pushdown),在对联合索引遍历的过程当中,根据索引中包含的字段对查询语句的其余条件进行判断,以此过滤不知足条件的记录,减小回表次数。对于上述例子来讲,就是innoDB在联合索引搜索树根据最左前缀定位到D3,接着判断age字段不等于23,继续向后遍历,D4中name符合"张%",但age不等于23,继续直到D5,name与age都符合,这时才会回表。以上过程只须要回表1次。

总结

三种优化方式:

  • 覆盖索引
  • 最左前缀原则
  • 索引下推
    以上三种方式都有同一个原则,也是数据库设计的重要原则之一:尽可能少地访问资源达到一样的效果

查询语句中where里面顺序和联合索引顺序不一致,优化器会自动作优化。


本文为极客时间《MySQL实战45讲》 的学习笔记,其中含有部分原文,若有侵权行为请联系我马上删除
再次感谢丁奇大佬
第一节:一条SQL查询语句的执行过程

相关文章
相关标签/搜索