上一篇介绍了InnoDB索引的数据结构模型,这一篇将继续介绍MySQL索引有关的概念。
在开始之前,先看一下这个问题:
在下面这个表T中,如果执行select * from T where k between 3 and 5
,需要执行几次树的搜索操作,会扫描多少行?
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');

这条语句的执行流程:
- 在k树索引树上找到 k=3 的记录,取得ID = 300
- 再到ID索引树查到 ID=300 对应的R3
- 在 k 索引树取下一个值 k=5,取得 ID=500
- 再回到 ID 索引树查到 ID=500 对应的 R4
- 在 k 索引树取下一个值 k=6,不满足条件,循环结束
在这个过程中,回到主键索引树搜索的过程,称为回表。可以看到,这个查询过程读了 k 索引树的 3 条记录(步骤1、3、5),回表了两次(步骤2、4)。
覆盖索引
如果执行的语句是 select ID from T where k between 3 and 5
,这时只需要查ID的值,而ID的值已经在k索引树上了,因此可以直接提供查询结果,不需要回表。也就是说,在这个查询里面,索引k已经“覆盖了”我们的查询需求,我们称为覆盖索引。
由于覆盖索引可以减少树的搜索次数,显著提升查询性能,所以使用覆盖索引是一个常用的性能优化手段。
最左前缀原则
B+树索引结构,可以利用索引的“最左前缀”,来定位记录。
为了直观说明这个概念,我们用(name,age)这个联合索引来分析。

可以看到,索引项是按照索引定义里面出现的字段顺序排序的。
当你的逻辑需求是查到所有名字是“张三”的人时,可以快速定位到ID4,然后向后遍历得到所有需要的结果。
如果你要查的是所有人名字第一个字是“张”的人,你的SQL语句的条件是where name like ‘张%’
。这时也能够用上索引,查到第一个符合条件的记录是ID3,然后向后遍历,直到不满足条件为止。
可以看到,不只是索引的全部定义,只要满足最左前缀,就可以利用索引来加速检索。这个最左前缀可以是联合索引的最左N个字段,也可以是字符串索引的最左M个字符。
基于最左前缀原则,如何安排索引内的字段顺序是关键。
- 如果通过调整顺序,可以少维护一个索引,那么这个顺序往往就是需要优先考虑采用的。比如有了(a,b)联合索引后,一般就不需要单独在a上建立索引了
- 其次考虑空间。比如 a,b 两个字段可以建立(a,b)、(b)和(a)、(b,a)两种,此时就要考虑a,b的字段长度,长的只建立一次,短的建立两次,节约空间。
索引下推
上一段中说到满足最左前缀原则的时候,可以使用索引定位记录。那么不符合最左前缀的部分,会怎么样呢?
比如还是以(name,age)为例。如果现在有一个需求:检索出表中“名字第一个字是张,而且年龄是10岁的所有男孩”。那么,SQL语句是这么写的:
mysql> select * from tuser where name like '张%' and age=10 and ismale=1;
根据最左前缀原则,这个语句在搜索索引树的时候,只能用“张”,找到第一个满足条件的记录ID3。然后,
- 在MySQL 5.6之前,只能从ID3开始一个个回表。到主键索引上找出数据行,再比对字段值。
age = 10
这个条件会回表难道记录后再比较。 - 在MySQL 5.6引入的索引下推优化(index condition pushdown),可以在索引遍历过程中,对索引中包含的字段先做判断,直接过滤掉不满足条件的记录,减少回表次数。
age = 10
这个条件会先比较,满足条件再回表。
网友评论