【MySQL进阶-03】深入理解mysql的索引分类,覆盖索引,覆盖索引失效,回表,MRR

本文涉及的产品
RDS MySQL DuckDB 分析主实例,基础系列 4核8GB
RDS AI 助手,专业版
RDS Agent(兼容OpenClaw),2核4GB
简介: 【MySQL进阶-03】深入理解mysql的索引分类,覆盖索引,覆盖索引失效,回表,MRR

一,索引

在阅读本文之前,最好先了解上面栏目中的一,二,六这三篇,先理解索引的本质,explain关键字的使用,以及六里面的 bufferpool 的缓存机制以及数据的加载机制。


1,聚簇索引和非聚簇索引

在innodb 中,mysql主要是通过索引这种数据结构增加查询效率,索引主要由聚簇索引和非聚簇索引所构成。聚簇索引主要是通过显式id的来存储,如果表中有id,并且给这个id加一个键,那么这个id就作为主键(聚簇)索引;如果没有定义一个id,那么就会去表中找是否存在 唯一键,如果有唯一键那么这个唯一索引作为主键索引,如果也没有唯一索键,那么就会通过一个隐式的id来存储。


如下,在一张 user 用户表中没有id,然后给表中的 name 字段一个唯一键


通过查看表中的索引可知,如下图,是将name这个字段的这一列作为一个主键索引的。


show keys from user;

ef1a4376df724076820722eb485438db.png

如果表中没有id字段,也没有字段上面有唯一键,那么innodb存储引擎会使用一个隐式的id作为主键索引的。


2,辅助索引和二级索引

除了聚簇索引,那么就是一些非聚簇索引,如一些辅助索引和二级索引。这里就涉及到一个 回表问题 ,比如说给库存表中的 sku_code商品码加一个索引,那么就需要先从这个 sku_code 的这列所对应的 b+ 树中先找到他的值,于此同时需要返回他的主键值,通过他的主键值再去主键(聚簇)索引对应的那棵b+树中找到对应的信息,因为表中所有的信息都存储在聚簇索引的叶子节点上,这个就称为回表,需要通过两次的IO进行获取数据。


3,回表

bee1914c0cd34572b107204dccd68d5e.png

接下来详细的描述一下到底什么是回表。如上图,一张库存表,如果在 sku_code 这里建一个普通的索引,

create index idx_sku_code on stock (sku_code) ;

如下图,那么如果在通过这个查 sku_code 这个字段获取创建时间时,需要先走这个普通索引对应的B+树,即sku_code列对应的B+树,将值查找出

select cteated_time from stock where sku_code = 'zhs01';

由于B+树的值都存储在叶子结点上面,因此在这里也会找到叶子结点为 sku_code = ‘zhs01’ 的这个值。此时叶子结点中存了key值和value值,key值对应的就是这个sku_code的值zhs01,value对应的就是这个主键索引的值。后面需要通过这个主键值回表到主键索引对应的B+树中,再次定位到该值的叶子节点。(绿色部分为二级索引的B+树,橙色部分为主键索引的B+树)


d92b258aa45f4c9ebcd124c8c9f70ef9.png

获取到这个id之后,再通过 主键所对应的B+树 ,在叶子节点上获取key为id = 12的结点,该节点对应的value值就表中那一行所对应的的值。


总而言之就是:普通索引就是为了更好的更快的找到主键索引。


4, MRR(重点)

由于索引是以页为单位的,一页的大小为16k。在回表的时候,需要返回主键索引的id,就会产生一个问题,就是在读取数据的时候,可能会产生一个 随机IO 。由于在读取磁盘的数据时,假设磁盘的扇区只有一个,每次可以读取磁盘的512个字节,那么读取一页数据就需要读取 16 × 1024 ÷ 512 = 32 次。


而索引存储在磁盘上,每个回表携带一个id,假设数据都是第一次读取,在不考虑bufferpool的情况下,那么就需要从磁盘中读取一页数据,那么每一个回表的数据就要与磁盘交互32多次,而如果存在多个回表的的主键id不在一个目录页上面,那么就需要 32乘以回表的个数,这样就大大的降低了查询的效率。


这就解释了为什么有时候发现一条sql语句在该字段加了索引,where后面也有这个字段,但是通过explain执行发现他不走索引,而是走的全表扫描,这主要就是因为再回表时产生的这个 随机IO 的原因


MRR:Disk-Sweep Multi-Range Read (MRR,多范围读取),其原理就是先读取一部分二级索引记录,将它们的主键值排好序之后再统一执行回表操作,这样就可以减少磁盘的交互次数,并且磁盘内一页的数据大小是按顺序排好的,那么在通过这个有序的页查数据时,就可以将这种 随机IO 转换成 顺序IO。


举个例子


假设某段时间内有10个回表的操作,并且数据的页目录有10页,此时由于10个回表操作那么会产生10个主键id值,那么在正常的情况下,假设值都分布在在页目录的随机位置,因此需要的回表次数是 10 次,如果此时数据都不在bufferpool的情况下,需要进行磁盘的IO次数就是 10×32 = 320 次io。


但是在引入MRR之后,会先将这10个回表的id先进行一个排序,然后排好序之后进行一次回表操作。这样就可以通过排好序的最大值和最小值获取到这些分别分布在哪些页上面,因为页与页之间也是有一个双向链表,其值都是排好顺序的,如果发现这两个值分别在同一个页目录上面,或者相邻的两个页面,那么这个十个值就都会在同一个页目录上或者相邻的两个页目录上。如果最大值和最小值刚好就是在一个页目录上,那么只需要回表这一次就可以了,其十个值都可以在这一个页目录上面找到,数据不在bufferpool的情况下,其需要的磁盘IO次数为 32 次;如果最大和最小值分别是在相邻的两个页目录上面,那么个也是只需要加载两次页目录就可以将这10个值找到,即只需要回表两次,两个页目录的数据不在bufferpool的情况下,其需要的磁盘IO次数为 2×32 = 64,其后面的照推之,这样就可以发现这个顺序IO的磁盘读写次数是小于上面的随机IO了。 这主要是为了减少回表的次数,如果bufferpool中不存在这些或者某些数据,那么同时也减少了随机IO,减少磁盘的交互数,但是并不能完全减少,如果这些id分别存在不同的页目录下面,那么这样加MRR和不加MRR的效率就一样了。


顺序IO大约是随机IO的40-100倍,这样就大大的提升了查询的效率。


总结就是一句话:如果数据已经加载在bufferpool中,那么MRR主要是为了减少这个回表的次数,如果数据在bufferpool中不存在,那么不仅仅减少了回表的次数,同时也减少了随机IO,减少磁盘的交互数


5,联合索引

就是由多个字段组合起来的一个联合索引。根据B+的底层,在一个联合索引中,必须前面的字段一样,才能对后面的字段进行比较,如果使用范围查询,那么范围后面的字段值就会失效。即须遵守最左前缀原则。这个比较简单就不多说了。


6,哈希索引

在innodb中,不仅存在B+树索引,同时也存在一个 自适应的hash索引 。在innodb这个存储引擎中,存在一个对热点数据的一个监控,如果某个表或者某一列对应的索引使用的特别频繁,那么就会将这个值加入到这个缓存里面,后面查询就可以直接利用这个hash算法直接定位到需要查询的值。其缓存的底层结构和hashMap的底层一样,由数组 + 链表组成,如果发生哈希碰撞,那么就由数组转成链表。


但是由于hash索引不能支持范围查询,因此mysql的索引最终是选择的B+树作为索引,而这个自适应索引是内部的一条命令,不提供外部使用,当然可以开启和禁用它。


7,密集索引和稀疏索引

密集索引:如果索引里面即存储了key,又存储了value,那么这个索引就是密集索引。如主键索引


稀疏索引:如果索引里面只存储key,不存储value,那么这个索引就是稀疏索引。所有的二级索引都是稀疏索引,myIsam中的索引都是稀疏索引


8,覆盖索引

什么是覆盖索引,在此之前,需要先理解上面的回表问题,就是查完二级索引之后,又要去主键索引中查询一级索引,这样就需要回表。那么这里就可以利用到覆盖索引了。覆盖索引一般针对的是辅助索引,整个查询结果只通过辅助索引就能拿到结果,不需要通过辅助索引树找到主键,再通过主键去主键索引树里获取其它字段值


再回到上面的这个sql,已知在 sku_code 这个字段加了索引,目前需要查找这个创建时间时间,这样执行下去的话,就需要先在 sku_code 这一列的B+树上面先找到这一列的id,然后通过回表携带这个id去主键索引对应的B+树里面去查询这一条数据,那么这样就会产生一个回表的时间。

select cteated_time from stock where sku_code = 'zhs01';

但是,如果将之前的单值索引变成联合索引,如下

create index idx_sku_code_created_time on stock (sku_code,created_time) ;

那么这个联合索引对应的B+树就是下面这个样子,那么就是在找到sku_code的值的时候,那么这个created_time的这个值也跟随着找出了。这个就是覆盖索引。这样就不需要携带这个id去回表,通过回表的方式从主键(聚簇)索引中获取这个值了。

621b9b384e444031b08b050aa3ee472e.png


再通过explain查看这条sql语句,如下图,发现这个type为ref,这条语句中走了非唯一索引,通过key的值,得知走的索引就是刚刚创建的二级索引,而最终通过这个Extra这个关键字,里面的值为Using index ,可以得知是走了覆盖索引的。这个具体的值的详解可以查看我之前的写的一篇博客https://blog.csdn.net/zhenghuishengq/article/details/124552080

explain select sku_code,created_time from stock where sku_code = 'zhs01'; 

2d5d39d7fc7b4ee285aa0a056875da48.png


9,覆盖索引失效问题

由上图可以覆盖索引是为了解决回表的次数带来的查询效率问题,但是在实际开发中也可能出现覆盖索引失效的问题,如下面这句,覆盖索引肯定是会失效的,因为表中还有其他的值要查询出其他字段,其他字段不能通过这个覆盖索引查出,那么肯定会通过回表的方式,在聚簇索引中将那一列数据查出,这样也增加了回表的次数

select * from stock where sku_code = 'zhs01';

还有下面的多一个字段问题,也是会出现这个回表问题的,那么也会出现索引覆盖失效

select sku_code,created_time,sale from stock where sku_code = 'zhs01'; 

这里可以发现这里是走索引的,但是由于这个Extra的这个值为NUll,不是Using index,可知这个覆盖索引是失效的,那必然是通过回表来查询所有的值的。并且这联合索引的字段需要遵循最左前缀原则,否则就不仅仅是覆盖索引失效了,而是整个联合索引直接失效。

4c95ada337814c0a9eb99efe433a782d.png


所以为了解决这种覆盖索引失效的问题,就是尽量少用select * ,尽量用覆盖索引中已有的字段,不要用覆盖索引中不存在的字段。


相关实践学习
每个IT人都想学的“Web应用上云经典架构”实战
本实验从Web应用上云这个最基本的、最普遍的需求出发,帮助IT从业者们通过“阿里云Web应用上云解决方案”,了解一个企业级Web应用上云的常见架构,了解如何构建一个高可用、可扩展的企业级应用架构。
MySQL数据库入门学习
本课程通过最流行的开源数据库MySQL带你了解数据库的世界。   相关的阿里云产品:云数据库RDS MySQL 版 阿里云关系型数据库RDS(Relational Database Service)是一种稳定可靠、可弹性伸缩的在线数据库服务,提供容灾、备份、恢复、迁移等方面的全套解决方案,彻底解决数据库运维的烦恼。 了解产品详情: https://www.aliyun.com/product/rds/mysql 
相关文章
|
10月前
|
存储 SQL 关系型数据库
mysql底层原理:索引、慢查询、 sql优化、事务、隔离级别、MVCC、redolog、undolog(图解+秒懂+史上最全)
mysql底层原理:索引、慢查询、 sql优化、事务、隔离级别、MVCC、redolog、undolog(图解+秒懂+史上最全)
mysql底层原理:索引、慢查询、 sql优化、事务、隔离级别、MVCC、redolog、undolog(图解+秒懂+史上最全)
|
10月前
|
存储 关系型数据库 MySQL
MySQL数据库索引的数据结构?
MySQL中默认使用B+tree索引,它是一种多路平衡搜索树,具有树高较低、检索速度快的特点。所有数据存储在叶子节点,非叶子节点仅作索引,且叶子节点形成双向链表,便于区间查询。
267 4
|
存储 关系型数据库 MySQL
阿里面试:MySQL 一个表最多 加几个索引? 6个?64个?还是多少?
阿里面试:MySQL 一个表最多 加几个索引? 6个?64个?还是多少?
阿里面试:MySQL 一个表最多 加几个索引? 6个?64个?还是多少?
|
关系型数据库 MySQL 数据库
Mysql的索引
MYSQL索引主要有 : 单列索引 , 组合索引和空间索引 , 用的比较多的就是单列索引和组合索引 , 空间索引我这边没有用到过 单列索引 : 在MYSQL数据库表的某一列上面创建的索引叫单列索引 , 单列索引又分为 ● 普通索引:MySQL中基本索引类型,没有什么限制,允许在定义索引的列中插入重复值和空值,纯粹为了查询数据更快一点。 ● 唯一索引:索引列中的值必须是唯一的,但是允许为空值 ● 主键索引:是一种特殊的唯一索引,不允许有空值 ● 全文索引: 只有在MyISAM引擎、InnoDB(5.6以后)上才能使⽤用,而且只能在CHAR,VARCHAR,TEXT类型字段上使⽤用全⽂文索引。
|
10月前
|
存储 SQL 关系型数据库
MySQL 核心知识与索引优化全解析
本文系统梳理了 MySQL 的核心知识与索引优化策略。在基础概念部分,阐述了 char 与 varchar 在存储方式和性能上的差异,以及事务的 ACID 特性、并发事务问题及对应的隔离级别(MySQL 默认 REPEATABLE READ)。 索引基础部分,详解了 InnoDB 默认的 B+tree 索引结构(多路平衡树、叶子节点存数据、双向链表支持区间查询),区分了聚簇索引(数据与索引共存,唯一)和二级索引(数据与索引分离,多个),解释了回表查询的概念及优化方法,并分析了 B+tree 作为索引结构的优势(树高低、效率稳、支持区间查询)。 索引优化部分,列出了索引创建的六大原则
269 2
|
11月前
|
存储 关系型数据库 MySQL
MySQL覆盖索引解释
总之,覆盖索引就像是图书馆中那些使得搜索变得极为迅速和简单的工具,一旦正确使用,就会让你的数据库查询飞快而轻便。让数据检索就像是读者在图书目录中以最快速度找到所需信息一样简便。这样的效率和速度,让覆盖索引成为数据库优化师傅们手中的尚方宝剑,既能够提升性能,又能够保持系统的整洁高效。
347 9
|
12月前
|
机器学习/深度学习 关系型数据库 MySQL
对比MySQL全文索引与常规索引的互异性
现在,你或许明白了这两种索引的差异,但任何技术决策都不应仅仅基于理论之上。你可以创建你的数据库实验环境,尝试不同类型的索引,看看它们如何影响性能,感受它们真实的力量。只有这样,你才能熟悉它们,掌握什么时候使用全文索引,什么时候使用常规索引,以适应复杂多变的业务需求。
312 12
|
存储 SQL 关系型数据库
MySQL 中的回表是什么?
在 MySQL 中,“回表”是指通过二级索引查询时,因二级索引仅存储索引字段值和主键值,需再根据主键到聚簇索引查找完整行数据的过程。此操作涉及两次索引查找,可能增加 IO 消耗,影响性能。优化方法包括使用覆盖索引或联合索引,避免回表,提升查询效率。合理设计索引对高并发、大数据量场景下的数据库性能至关重要。
742 17
|
SQL 存储 关系型数据库
MySQL选错索引了怎么办?
本文探讨了MySQL中因索引选择不当导致查询性能下降的问题。通过创建包含10万行数据的表并插入数据,分析了一条简单SQL语句在不同场景下的执行情况。实验表明,当数据频繁更新时,MySQL可能因统计信息不准确而选错索引,导致全表扫描。文章深入解析了优化器判断扫描行数的机制,指出基数统计误差是主要原因,并提供了通过`analyze table`重新统计索引信息的解决方法。
351 3

推荐镜像

更多