【MySQL45讲】深入浅出索引(下)

本文详细介绍了MySQL的回表、覆盖索引、最左前缀原则以及索引下推等概念。通过具体例子说明了如何优化查询语句以减少回表次数,提高查询性能。此外,还讨论了联合索引的使用场景和创建冗余索引的权衡。最后,针对特定查询模式,分析了不同索引设计的必要性。
摘要由CSDN通过智能技术生成

一、回表

1.1 表结构

表T的初始化语句

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')

在这个表中,如果执行语句

select * from T where k between 3 and 5;

需要执行几次树的搜索操作,扫描多少行?

1.2 索引组织结构

再看看InnoDB的索引组织结构如下:
在这里插入图片描述

1.3 执行流程

这条SQL查询语句的执行流程:

  1. 在k索引树上找到k=3的记录,取得ID=300
  2. 再回到ID索引树查到ID=300对应的R3
  3. 在k索引树取下一个值k=5,取得ID=500
  4. 再回到ID索引树查到ID=500对应的R4
  5. 在k索引树取下一个值k=6,不满足条件,循环结束

在这个过程中,回到主键索引树搜索的过程,我们称为回表。可以看到,这个查询过程读了k 索引树的3条记录(步骤1、3和5),回表了两次(步骤2和4)

在这个例子中,由于查询结果所需要的数据只在主键索引上有,所以不得不回表

二、覆盖索引

2.1 覆盖索引

有没有可能经过索引优化,避免回表过程呢?

如果执行的语句是:

select ID from T where k between 3 and 5;

这时只需要查ID的值,而ID的值已经在k索引树上了,因此可以直接提供查询结果,不需要回表。也就是说,在这个查询里面,索引k已经"覆盖"了我们的查询需求,我们称为覆盖索引

由于覆盖索引可以减少树的搜索次数,显著提升查询性能,所以使用覆盖索引是一个常用的性能优化手段

需要注意的是,在引擎内部使用覆盖索引在索引k上其实读了三个记录,R3~R5(对应的索引k上的记录项),但是对于MySQL的Server层来说,它就是找引擎拿到了两条记录,因此MySQL认为扫描行数是2

2.2 联合索引

基于上面覆盖索引的说明,我们来讨论一个问题:
在一个市民信息表上,是否有必要将身份证号和名字建立联合索引?

假设这个市民表的定义是这样的:

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

身份证号是市民的唯一标识,也就是说,如果有根据身份证号查询市民信息的需求,只要在身份证号字段上建立索引就够了,而再建立一个(身份证号、姓名)的联合 索引,是不是浪费空间?
如果现在有一个高频请求,要根据市民的身份证号查询他的姓名,这个联合索引就有意义了,可以在这个高频请求上用到覆盖索引,不再需要回表查整行记录,减少语句的执行时间

当然,索引字段的维护总是有代价的,因此,在建立冗余索引来支持覆盖索引时就需要权衡考虑

三、最左前缀原则

3.1 索引组合

如果为每一种查询都设计一个索引,索引是不是太多了?
如果现在要按照市民的身份证号去查他的家庭地址呢?虽然这个查询需求在业务中出现的概率 不高,但总不能让它走全表扫描吧?反过来说,单独为一个不频繁的请求创建一个(身份证号,地址)的索引又感觉有点浪费,应该怎么做呢?

解决上面问题的方式是:
B+树这种索引结构,可以利用索引的"最左前缀",来定位记录

为了直观地说明这个概念,使用(name,age)这个联合索引来分析
在这里插入图片描述

3.2 模糊查询

可以看到,索引项是按照索引定义里面出现的字段顺序排序的

  • 查到所有名字是"张三"的人

当逻辑需求是查到所有名字是"张三"的人时,可以快速定位到ID4,然后再遍历得到所有需要的结果

  • 查名字第一个字是"张"的人

如果要查的是所有名字第一个字是"张"的人,SQL语句的条件是"where name like ‘张%’",这时,也能够用上这个索引,查找到第一个符合条件的记录是ID3,然后向后遍历,直到不满足条件为止

可以看到,不只是索引的全部定义,只要满足最左前缀,就可以利用索引来加速检索
这个最左前缀可以是联合索引的最左N个字段,也可以是字符串索引的最左M个字符

3.2 索引字段顺序

基于上面对最左前缀索引的说明,在建立联合索引的时候,如何安排索引内的字段顺序?

  • 评估标准

索引的复用能力

  • 第一原则

因为可以支持最左前缀,所以当已经有了(a,b)这个联合索引后,一般就不需要单独在a上建立索引
因此,第一原则是,如果通过调整顺序,可以少维护一个索引,那么这个顺序往往就是需要优先考虑采用的

所以前面的问题中,要为高频请求创建(身份证号,姓名)这个联合索引,并用这个索引支持"根据身份证号查询地址"的需求

  • 空间原则

如果既有联合查询,又有基于a、b各自的查询呢?
查询条件里面只有b的语句,是无法使用(a,b)这个联合索引的,这时候不得不维护另外一个索引,也就是说你需要同时维护 (a,b)、(b) 这两个索引

这时候要考虑的原则就是空间了,比如上面这个市民表的情况,name字段是比age字段大的,那么创建一个(name,age)的联合索引和一个(age)的单字段索引

四、索引下推

4.1 联合索引最左前缀

满足最左前缀原则的时候,最左前缀可以用于在索引中定位记录,但是那些不符合最左前缀的部分,会怎么样呢?

还是以市民表的联合索引(name, age)为例,如果现在有一个需求:
检索出表中名字第一个字是"张",而且年龄是10岁的所有男孩

那SQL语句是这么写的:

mysql> select * from tuser where name like '张%' and age=10 and ismale=1;

根据前缀索引规则,所以这个语句在搜索索引树的时候,只能用"张",找到第一个满足条件的记录ID3,当然,这还不错,总比全表扫描要好

然后呢? 当然是判断其他条件是否满足

4.2 索引下推

在MySQL 5.6版本之前:
只能从ID3开始一个个回表。到主键索引上找出数据行,再对比字段值

在MySQL 5.6版本及之后:
引入的索引下推优化(index condition pushdown),可以在索引遍历过程中,对索引中包含的字段先做判断,直接过滤掉不满足条件的记录,减少回表次数

4.3 索引下推执行流程

下面是无索引下推和索引下推的执行流程图

  • 无索引下推执行流程
    在这里插入图片描述

  • 索引下推执行流程
    在这里插入图片描述
    PS:在上面两个执行流程图中,每一个虚线箭头表示回表一次

  • 回表次数

无索引下推执行流程图中,在(name,age)索引里面特意去掉了age的值,这个过程InnoDB并不会去看age的值,只是按顺序把name第一个字是"张"的记录一条条取出来回表,因此,需要回表4次

  • 区别

索引下推和无索引下推的区别在于:
InnoDB在(name,age)索引内部就判断了age是否等于10,对于不等于10 的记录,直接判断并跳过,只需要对ID4、ID5这两条记录回表取数据判断,就只需要回表2次

五、问答

  • 问题

实际上主键索引也是可以使用多个字段的,DBA小吕在入职新公司的时候,就发现自己接手维护的库里面,有这么一个表,表结构定义类似这样的:

CREATE TABLE `geek` (
 `a` int(11) NOT NULL,
 `b` int(11) NOT NULL,
 `c` int(11) NOT NULL,
 `d` int(11) NOT NULL,
 PRIMARY KEY (`a`,`b`),
 KEY `c` (`c`),
 KEY `ca` (`c`,`a`),
 KEY `cb` (`c`,`b`)
) ENGINE=InnoDB;

公司的同事告诉他说,由于历史原因,这个表需要a、b做联合主键,这个小吕理解, 但学过本章内容的小吕又纳闷了,既然主键包含了a、b这两个字段,那意味着单独在字段c上创建一个索引,就已经包含了三个字段了呀,为什么要创建"ca"、"cb"这两个索引呢?
同事告诉他是因为在业务里有这两种语句:

select * from geek where c=N order by a limit 1;
select * from geek where c=N order by b limit 1;

这位同事的解释对吗,为了这两个查询模式,这两个索引是否都是必须的么?为什么呢?

  • 答案

这个是关于对联合主键索引和InnoDB索引组织表的理解
表记录:

abcd
123d
132d
143d
213d
222d
234d

主键 a,b的聚簇索引组织顺序相当于order by a,b,也就是先按a排序,再按b排序,c无序

索引 ca 的组织是先按c排序,再按a排序,同时记录主键

ca主键部分b
213
222
312
314
321
423

这个跟索引c的数据是一模一样的

索引 cb 的组织是先按c排序,在按b排序,同时记录主键

cb主键部分a
222
231
312
321
341
432

所以,结论是ca可以去掉,cb需要保留

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

sysu_lluozh

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值