6 05 | 深入浅出索引(下)
Posted 程序杰杰
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了6 05 | 深入浅出索引(下)相关的知识,希望对你有一定的参考价值。
在上一篇文章中,我和你介绍了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\');
现在,我们一起来看看这条SQL查询语句的执行流程:
-
在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已经“覆盖了”我们的查询需求,我们称为覆盖索引。
由于覆盖索引可以减少树的搜索次数,显著提升查询性能,所以使用覆盖索引是一个常用的性能优化手段。
需要注意的是,在引擎内部使用覆盖索引在索引k上其实读了三个记录,R3~R5(对应的索引k上的记录项),但是对于MySQL的Server层来说,它就是找引擎拿到了两条记录,因此MySQL认为扫描行数是2。
备注:关于如何查看扫描行数的问题,我将会在第16文章《如何正确地显示随机消息?》中,和你详细讨论。
基于上面覆盖索引的说明,我们来讨论一个问题:在一个市民信息表上,是否有必要将身份证号和名字建立联合索引?
假设这个市民表的定义是这样的:
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
我们知道,身份证号是市民的唯一标识。也就是说,如果有根据身份证号查询市民信息的需求,我们只要在身份证号字段上建立索引就够了。而再建立一个(身份证号、姓名)的联合索引,是不是浪费空间?
如果现在有一个高频请求,要根据市民的身份证号查询他的姓名,这个联合索引就有意义了。它可以在这个高频请求上用到覆盖索引,不再需要回表查整行记录,减少语句的执行时间。
当然,索引字段的维护总是有代价的。因此,在建立冗余索引来支持覆盖索引时就需要权衡考虑了。这正是业务DBA,或者称为业务数据架构师的工作。
最左前缀原则
看到这里你一定有一个疑问,如果为每一种查询都设计一个索引,索引是不是太多了。如果我现在要按照市民的身份证号去查他的家庭地址呢?虽然这个查询需求在业务中出现的概率不高,但总不能让它走全表扫描吧?反过来说,单独为一个不频繁的请求创建一个(身份证号,地址)的索引又感觉有点浪费。应该怎么做呢?
这里,我先和你说结论吧。B+树这种索引结构,可以利用索引的“最左前缀”,来定位记录。
为了直观地说明这个概念,我们用(name,age)这个联合索引来分析。
可以看到,索引项是按照索引定义里面出现的字段顺序排序的。
当你的逻辑需求是查到所有名字是“张三”的人时,可以快速定位到ID4,然后向后遍历得到所有需要的结果。
如果你要查的是所有名字第一个字是“张”的人,你的SQL语句的条件是"where name like ‘张%’"。这时,你也能够用上这个索引,查找到第一个符合条件的记录是ID3,然后向后遍历,直到不满足条件为止。
可以看到,不只是索引的全部定义,只要满足最左前缀,就可以利用索引来加速检索。这个最左前缀可以是联合索引的最左N个字段,也可以是字符串索引的最左M个字符。
基于上面对最左前缀索引的说明,我们来讨论一个问题:在建立联合索引的时候,如何安排索引内的字段顺序。
这里我们的评估标准是,索引的复用能力。因为可以支持最左前缀,所以当已经有了(a,b)这个联合索引后,一般就不需要单独在a上建立索引了。因此,第一原则是,如果通过调整顺序,可以少维护一个索引,那么这个顺序往往就是需要优先考虑采用的。
所以现在你知道了,这段开头的问题里,我们要为高频请求创建(身份证号,姓名)这个联合索引,并用这个索引支持“根据身份证号查询地址”的需求。
那么,如果既有联合查询,又有基于a、b各自的查询呢?查询条件里面只有b的语句,是无法使用(a,b)这个联合索引的,这时候你不得不维护另外一个索引,也就是说你需要同时维护(a,b)、(b) 这两个索引。
这时候,我们要考虑的原则就是空间了。比如上面这个市民表的情况,name字段是比age字段大的 ,那我就建议你创建一个(name,age)的联合索引和一个(age)的单字段索引。
索引下推
上一段我们说到满足最左前缀原则的时候,最左前缀可以用于在索引中定位记录。这时,你可能要问,那些不符合最左前缀的部分,会怎么样呢?
我们还是以市民表的联合索引(name, age)为例。如果现在有一个需求:检索出表中“名字第一个字是张,而且年龄是10岁的所有男孩”。那么,SQL语句是这么写的:
mysql> select * from tuser where name like \'张%\' and age=10 and ismale=1;
你已经知道了前缀索引规则,所以这个语句在搜索索引树的时候,只能用 “张”,找到第一个满足条件的记录ID3。当然,这还不错,总比全表扫描要好。
然后呢?
当然是判断其他条件是否满足。
在MySQL 5.6之前,只能从ID3开始一个个回表。到主键索引上找出数据行,再对比字段值。
而MySQL 5.6 引入的索引下推优化(index condition pushdown), 可以在索引遍历过程中,对索引中包含的字段先做判断,直接过滤掉不满足条件的记录,减少回表次数。
图3和图4,是这两个过程的执行流程图。
在图3和4这两个图里面,每一个虚线箭头表示回表一次。
图3中,在(name,age)索引里面我特意去掉了age的值,这个过程InnoDB并不会去看age的值,只是按顺序把“name第一个字是’张’”的记录一条条取出来回表。因此,需要回表4次。
图4跟图3的区别是,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;
我给你的问题是,这位同事的解释对吗,为了这两个查询模式,这两个索引是否都是必须的?为什么呢?
你可以把你的思考和观点写在留言区里,我会在下一篇文章的末尾和你讨论这个问题。感谢你的收听,也欢迎你把这篇文章分享给更多的朋友一起阅读。
上期问题时间
上期的问题是,通过两个alter 语句重建索引k,以及通过两个alter语句重建主键索引是否合理。
在评论区,有同学问到为什么要重建索引。我们文章里面有提到,索引可能因为删除,或者页分裂等原因,导致数据页有空洞,重建索引的过程会创建一个新的索引,把数据按顺序插入,这样页面的利用率最高,也就是索引更紧凑、更省空间。
这道题目,我给你的“参考答案”是:
重建索引k的做法是合理的,可以达到省空间的目的。但是,重建主键的过程不合理。不论是删除主键还是创建主键,都会将整个表重建。所以连着执行这两个语句的话,第一个语句就白做了。这两个语句,你可以用这个语句代替 : alter table T engine=InnoDB。在专栏的第12篇文章《为什么表数据删掉一半,表文件大小不变?》中,我会和你分析这条语句的执行流程。
评论区留言中, @壹笙☞漂泊 做了很详细的笔记,@高枕 帮同学解答了问题,@约书亚 提了一个很不错的面试问题。在这里,我要和你们道一声感谢。
PS:如果你在面试中,曾有过被MySQL相关问题难住的经历,也可以把这个问题发到评论区,我们一起来讨论。如果解答这个问题,需要的篇幅会很长的话,我可以放到答疑文章展开。
MySQL———深入浅出索引
索引模型理论
什么是索引?
一句话简单来说,索引的出现其实就是为了提高数据查询的效率,就像书的目录一样。一本 500 页的书,如果你想快速找到其中的某一个知识点,在不借助目录的情况下,那我估计你可得找一会儿。同样,对于数据库的表而言,索引其实就是它的 “目录”。
索引的常见模型
索引的出现是为了提高查询效率,实现索引的方式由好多种,所以索引模型的概念很重要!
换句话说,可以用于提高读写效率的数据结构!就是索引想要的!常见、也是比较简单的有三种:哈希表、有序数组和搜索树。
那这三种有什么区别?
哈希表
哈希表是一种以键 - 值(key - value)存储数据的结构,我们只要输入待查找的 key ,就可以找其对应的 Value。思路很简单:Value 都在数组里,用一个哈希函数把 key 换算成一个确定的位置,然后把 value 放在数组的这个位置。
当然不可避免,多个 key 值经过哈希函数的换算,会出现一个值的情况。处理这种情况也很简单!拉出一个链表。这个链表存储 key 和 value 的信息! 这种方法需要精准的hash算法让值均匀的分布,不然容易出现极端的情况:一条单链表!
假设现在需要维护一个身份证信息和姓名的表,根据身份证号查找名字,这是对应的哈希索引如下:
此时若根据不同的身份证号对应的相同的名字,只需要对姓名所在的链表,用身份证号进行顺序遍历!
没错,这就是hashmap的底层实现!
需要注意的是,前面的 key (身份证号)是无序的!好处是:添加新的 User 时速度很快,只需要往后追加。但是缺点:因为是无序的,所以哈希索引做区间,查询的速度是很慢的。( 哈希出来的数组,顺序是没有实际意义的,因为key放在哪只与哈希函数有关、与索引计算有关;)
有序数组
有序数组在等值查询和范围查询的场景中的性能都非常优秀;还是身份证号的例子用有序数组索引来实现:
假设身份证号没重复,这个数组就是按照身份证号递增的顺序保存的。这时候要查找某身份证号对应的名字,用二分搜索效率高! O(log(N))
总结:有序数组能够解决hash函数不能满足支持快速范围查询。查找速度为log(n),但缺陷也很明显,针对插入和删除场景,需要挪动后面的整个记录,代价太高。有序数组适用于静态搜索引擎。(而且数组的长度是有限的,数组的下标只能是自然数)
搜索树
说起搜索树,我们会想到二叉搜索树(二叉搜索树的概念默认大家的知道)
还是上面身份证号的例子
整体查询事件复杂度为 O(log(N)) , 最坏的情况下是 O(N),退化成链表,因为没有平衡的限制!
当然,为了维持 O(log(N)) 的查询复杂度,你就需要保持这棵树是平衡二叉树。为了做这个保证,更新的时间复杂度也是 O(log(N))。
但是索引不止存在内存中,还要写在磁盘上,有IO操作!
所以对速度也有要求,那么速度的衡量是什么呢?是树的深度!树的深度越深,每次就需要访问过多的节点,即访问数据块过多,磁盘随机读取数据块又比较耗时;
为了让一个查询尽量的少读磁盘,就必须让查询过程访问尽量少的数据块我们会自然想到减少树的层数,进而减少树的深度!多叉树绝对是个不错的选择!同样多的数据,分的叉越多,深度越小!
N叉树由于在读写上的性能优点,以及适配磁盘的访问模式,已经被广泛应用在数据库引擎中了。
我们心里要有个概念,数据库底层存储的核心就是基于这些数据模型的。因为数据库就是存储数据的!每碰到一个新数据库,需要先关注它的数据模型,这样才能从理论上分析出这个数据库的适用场景!
也可以了解下 跳表、LSM 树等数据结构,也被用于引擎设计中。
应用
在MySQL中,索引是在存储引擎层实现的,因此没有统一的索引标准,即不同存储引擎的索引的工作方式并不一样;即使多个引擎支持同一种类型的索引,其底层的实现也可能不同。其中 InnoDB 存储引擎在 MySQL 数据库中使用最为广泛,也是其现版本默认的存储引擎,下面就以 InnoDB 为例,展开分析!
事务也是在存储引擎层实现的,因为不是所有的存储引擎都支持事务!
InnoDB 的索引模型
在 InnoDB 中,表都是根据主键顺序以索引的形式存放的,这种存储方式的表成为索引组织表。
索引组织表的理解:
在InnoDB存储引擎中,表都是根据主键顺序组织存放的,这种存储方式的表称为索引组织表(index organized table IOT)。
在InnoDB存储引擎中,每张表都有个主键(Primary key),如果在创建表时没有地定义主键,则InnoDB存储引擎会选择表中符合条件的列去创建主键。
条件:
首先判断表中是否有非空的唯一索引(Unique NOT NULL),如果有,则该列即为主键。
如果不符合上述条件,InnoDB存储引擎自动创建一个6字节大小的指针。
当表中存在多个非空的唯一索引的时候,InnoDB存储引擎会根据建表时所创建的第一个非空唯一索引作为主键。
InnoDB 使用了 B+ 树索引模型,所以数据都是存储在 B+ 数中的。
关于 InnoDB 的表结构:
在 InnoDB 中,每一张表其实就是多个 B+ 树,即一个主键索引树和多个非主键索引树。
(每一个索引在 InnoDB 里边对应一颗 B+ 树。)
如果不使用索引进行查询,则从主索引 B+ 树的叶子节点进行遍历。
概念总是含糊的,举个例子吧!
假设,我们有一个主键列为ID的表,表中有字段 k ,并且在 k 上有索引。
这个表的建表语句是:
mysql> create table T(
id int primary key,
k int not null,
name varchar(16),
index (k))engine=InnoDB;
表中R1 ~ R5 的 ( ID,K ) 值分别是 ( 100,1 )、( 200,2 )、( 300,3 )、( 500,5 ) 和 ( 600,6 ),因为有两个索引,因此存在两个B+树,示意图如下:
根据叶子节点的内容,索引类型分为:
主键索引(叶子节点存放的该行的数据);在 InnoDB 里边,主键索引也被称为 聚簇索引
非主键索引(叶子节点存放的是主键的值);非主键索引也被称为二级索引
那么,有一个问题:
基于主键索引和普通索引的查询有什么区别?
- 如果语句是select * from T where ID=500,即主键查询,则只需要搜索 ID 这颗 B+ 树;
- 如果语句是select * from T where k=5,即需要先搜索 k 索引树,得到 ID 的值为 500,再 ID 索引树搜索一次。这个过程成为回表。
主键索引 是唯一且有序的,非主键索引,是需要找到结果ID,回表,获取最后的结果的。
也就是说,**基于非主键索引的查询需要多扫描一棵树。**因此!我们再生活中应该尽量使用主键查询!
索引维护
B+ 树为了维护索引有序性,在插入新值得时候需要做必要得维护,来确保索引有序。
这就涉及到 B+ 为了满足节点数据有序做的维护操作;
逻辑(自增)主键 和 业务主键
逻辑主键(surrogate key):无意义的字段,即自增长字段,即identity。这其中还有一个选择GUID(Globally Unique Identifier)。 也叫代理主键。
业务主键(natrual key):有意义的字段,比如身份证 ID。也叫自然主键
逻辑主键和业务主键的使用场景与区别:
自行思考
自增主键是指自增列上定义得主键,在建表语句中一般时这么定义的:NOT NULL PRIMARY KEY AUTO_INCREMENT。
插入新纪录的时候可以不指定 ID 的值,系统会获得当前 ID 最大值加 1 作为下一条记录的 ID 值;
也就是说,自增主键的插入数据模式,正符合了我们前面提到的递增插入的场景。每次插入一条新记录,都是“追加操作”,都不涉及到挪动其他记录,也不会触发叶子节点的分裂。
这里关于页分裂提一嘴:
mysql 在底层又是以数据页为单位来存储数据的,一个数据页大小默认为 16k,当然你也可以自定义大小,也就是说如果一个数据页存满了,mysql 就会去申请一个新的数据页来存储数据。
如果主键自增主键的话,mysql 在写满一个数据页的时候,直接申请另一个新数据页接着写就可以了 !
如果主键不是自增主键的话,为了确保索引有序,mysql 就需要将每次插入的数据都放到合适的位置上。当往一个快满或已满的数据页中插入数据时,新插入的数据会将数据页写满,mysql 就需要申请新的数据页,并且把上个数据页中的部分数据挪到新的数据页上。这就造成了页分裂,这个大量移动数据的过程是会严重影响插入效率的。
而有业务逻辑的字段做主键,往往不容易保证有序插入,这样写数据成本相对高。
除了考虑性能外,还可以从存储空间的角度来看。“假设你的表中有一个唯一字段,比如字符串类型的身份证号,那么应该用身份证号做主键还是用自增字段做主键?
注意回想:每个非主键索引的叶子节点上都是主键的值。如果用身份证号做主键,每个二级索引的叶子节点占用约20字节,如果用整数做主键,只需要4个字节,如果长整型,则需要8字节。
显然,主键长度越小,普通索引鞥带叶子节点就越小,普通索引占用的空间也就越小。
总结一下
所以:业务字段不一定是递增的,有可能会造成主键索引的页分裂,导致性能不稳定。
二级索引存储的值是主键,如果使用业务字段,占用大小不好控制,如果业务字段过长可能会导致二级索引占用空间过大,利用率不高。
综上:从性能和存储空间方面综合考量,自增主键往往是更合理的选择。
当然,也有适合用业务字段做主键得到场景:比如,有些需求是这样的:
- 只有一个索引;
- 该索引必须是唯一索引。
没错,这就是典型的 KV 场景。由于没有其他索引,就不用考虑其他索引的叶子节点大小问题了。
这时候,要考虑 ”尽量使用主键查询“ 原则,直接将这个索引设置为主键,避免每次查询搜索两棵树。
覆盖索引
先看一个例子:
在下面这个表中,如果我执行 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');
现在,我们一起来看看这条 SQL 查询语句的执行流程:
在 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查名字建立联合索引很有必要!)
最左前缀原则
在我们涉及表的过程中,如果为每一个查询都设计一个索引,那索引岂不是太多了。虽然我们要查询的属性在业务中出现的概率很低,但是全表查询又比较麻烦!
莫慌!B+树这种索引结构,可以利用索引的 “ 最左前缀 ”,来定位记录。
先举个栗子:
用(name,age)这个联合索引来分析
索引项是按照索引定义里边出现得字段顺序排序得。**
当你的逻辑要求是查到所有名字是 " 张三 "的人时,可以快速的定位到 ID4,然后遍历得到所有需要的结果。
如果要查的是所有名字第一个是 “ 张 ” 的人,你的 SQL 语句的条件是 “ where name like '张 %‘。这时,你也能用上这个索引,查找到第一符合条件的记录是 ID3,然后向后遍历,直到不满意为止!
所以说!不只是索引的全部定义,只要满足最左前缀,就可以利用索引来加速检索。这个最左前缀可以是联合索引的最左 N 个字段,也可以是字符串索引的最左 N 个字段**
有一个问题:在建立联合索引的时候,如何安排索引内的字段顺序?
这里我们的评估标准是 ” 索引的服用能力 “。因为支持最左前缀,当已经有了(a,b)这个联合索引后,我们不需要单独在 a 上建立索引了。因此!第一原则:如果通过调整顺序,可以少维护一个索引,那么这个顺序往往就是需要优先考虑采用的。
回到开始!
我们可以用(身份证id,姓名)这个联合索引,因为支持最左前缀,此时我们就少维护了一个 ” 身份证id “ 的索引,可以用此联合索引支持 ” 根据身份证号查询地址 “ 的需求。
如果!
如果既有联合查询,又有基于 a、b (a,b)各自的查询呢?这时候不得不维护另外一个索引,自然就是(a,b)和(b)这两个索引。(因为支持最左前缀,不同单独在 a 上建索引)。
所以!第二个考虑原则就是空间了。如果 a 字段比 b 大,那么就可以创建一个 ( a, b )的联合索引和一个 ( b ) 的单字段索引。
索引下推
还是上面的栗子,其中用最左前缀在索引中定位了记录,那不满足索引前缀的部分会怎么样呢?
回到市民的联合索引 ( name,age) ;现有一个需求,要检索出 ” 名字第一个字是张,而且年龄是 10 岁的所有男孩 “;先写其 SQL 语句。
mysql> select * from tuser where name like '张%' and age=10 and ismale=1;
根据索引前缀:只能用 ” 张 “,找到第一个满足条件记录的 ID3.当然,这总比全表扫面要好!
在MySQL 5.6之前,只能从 ID3 开始一个一个回表。在主键索引上找到数据行,对比字段值。
而 5.6 引入的 ” 索引下推优化 “, **可以在索引遍历过程中,对索引中包含的字段先做判断,直接过滤掉不满足条件的记录,减少回表次数。 **
就是说!5.6 以前查到只要是 name 第一个字是张,管 age 满足与否,先查主键回表再说!而索引下推就是在找到满足 name 第一个字是张的时候,还会会其 age 进行筛选,选择合适的索引,并查其主键进行回表。
其过程如下图:
无索引下推执行流程
有索引下推执行流程
中间一个箭头就表示回表一次,显然!有索引下推流程的回表次数少!
小总结一下索引覆盖、最左前缀原则、索引下推:
索引覆盖:如果能通过检索索引就能得到要查询的值 ( 这个索引覆盖了要查询的值),大可不必再去数据表中去读取数据了,减少了回表的操作。
最左前缀原则:对于那些不经常查询的列,对其建立索引显得太浪费(索引也需要维护),全表查询又太慢,这时候可以,这时候就可以用 ”最左前缀” 来查询,通过索引的最左边的 N 个字段进行定位,由于一个属性的最左边 N 个字段比较模糊,经常和联合索引一块使用,这里会牵扯到联合索引的字段顺序的一个优化!
索引下推:索引下推是在非主键查询的时候使用的!因为主键是唯一的,所以在非主键索引确定的情况下,不需要下推;
因此,索引下推一般和最左前缀一块使用,满足最左前缀的时候,最左前缀可以用在索引中定位记录。而不满足最左前缀的部分就可以用索引下推;在不使用索引下推的时候,定位到满足前缀的第一个节点时,不管其他部分(不满足最左前缀的部分)满足与否,都会根据主键的值回表查询;
而使用索引下推后,会先检查其他部分满足与否,不满足的就不会进行回表那步操作
参考文献:林晓斌《MySQL实战 45 讲》
以上是关于6 05 | 深入浅出索引(下)的主要内容,如果未能解决你的问题,请参考以下文章