我们以下面这个建表语句为例
mysql> create table T (
ID int primary key,
k int NOT NULL DEFAULT 0,
s varchar(16) NOT NULL DEFAULT '',
index k(k))
engine=InnoDB;
insert into T values(100,1, 'aa'),(200,2,'bb'),(300,3,'cc'),(500,5,'ee'),(600,6,'ff'),(700,7,'gg');
表 T 中,如果我执行 select * from T where k between 3 and 5;
,需要执行几次树的搜索操作,会扫描多少行?
这条sql语句的执行流程是:
先从k的索引树上查找ID,再根据ID去主键索引树上查找相应的数据。
这个过程中,回到主键树搜索的过程称为回表。
在这个例子中,由于查询结果所需要的数据只在主键索引上有,所以不得不回表。
覆盖索引
如果执行的语句是select ID from T where k between 3 and 5;
,这时候只需要查ID的值,而ID值已经在k索引树上了,因此可以直接提供查询结果,不需要回表。也就是说,在这个查询里,索引k已经“覆盖了”我们的查询需求,我们称之为覆盖索引。
由于覆盖索引可以减少树的搜索次数,显著的提升查询性,所以覆盖索引是一个常用的优化手段。
如何判断是否要使用覆盖索引呢?
假设有一张存放市民身份信息的表,有一个高频请求,就是根据市民的身份证号查询他的姓名,那么建立一个(身份证号、姓名)的联合索引就有必要了。
当然,索引字段的维护总是有代价的。因此,再建立冗余索引来支持覆盖索引时就需要权衡考虑了。
最左前缀原则
看到这里你一定有一个疑问,为每一种查询都设计一个索引,索引是不是太多了?
B+树这种索引结构,可以利用索引的“最左前缀”来定位记录。
为了更直观的说明这个概念,我们我们用(name、age)这个联合索引来分析。
你的sql语句的条件是
where name like '张%'
,这时,你也能用上这个索引。可以看到,不止索引的全部定义,只要满足了最左前缀,就可以利用索引来加速检索。最左前缀可以是联合索引的最左n个字段,也可以是字符串索引的最左n个字符。
在建立联合索引的时候,如何安排索引内的字段顺序?
这里我们评估的是标标准是,索引的复用能力。因为可以支持最左前缀,所以当已经有了(a,b)这个联合索引后,一般就不需要单独在a上建索引了。因此,第一原则是,如果通过调整顺序,可以少维护一个索引,那么这个顺序往往就是需要优先考虑采用的。
那么如果既有联合查询,又有基于a、b各自的查询呢?
查询条件里只有b的语句,是无法使用联合索引(a,b)的这时候你不得不维护另一个索引,也就是说你需要同时维护(a,b)、(b)这两个索引。
这时候,我们要考虑的原则就是空间了。比如上面市民表这个情况,name字段是比age字段大的,那我就建议你创建一个(name,age)的联合索引和一个(age)的单字段索引。
索引下推
当满足最左前缀原则的时候,最左前缀可以用于在索引中定位记录。这时,你可能要问,那些不符合最左前缀的部分会怎么样呢?
我们还是以市民表的联合索引(name,age)为例。如果现在有一个查询mysql> select * from tuser where name like '张%' and age=10 and ismale=1;
,这个语句在搜索树的时候,只能用到“张”,找到第一个满足条件的记录ID3。
然后呢?
当然是判断其他条件是否满足。
在mysql5.6之前,只能从ID3一个个回表,到主键索引上找出数据行,再比对字段值。
而mysql5.6引入的索引下推优化(index condition pushdown),可以在索引遍历过程中,对索引中包含的字段先做判断,直接过滤掉不满足条件的记录,减少回表次数。
延伸阅读
最左索引和复合索引规则
感谢以下文章作者
https://www.zhihu.com/question/36996520