【MySQL进阶】MRR、索引跳跃式扫描、索引覆盖、索引下推详解

文章详细介绍了MySQL中的一些高级索引使用技巧,包括索引覆盖、索引下推、Multi-RangeRead(MRR)机制以及索引跳跃式扫描。这些机制有助于提高查询效率,减少回表操作,降低磁盘IO。文章还提醒了编写SQL时应避免的行为,以确保更好地利用索引。

【MySQL进阶】MRR、索引跳跃式扫描、索引覆盖、索引下推详解

🎈使用索引的正确姿势

其实到这里,对于如何使用索引才是正确的呢?总结如下:

  • ①查询SQL中尽量不要使用OR关键字,可以使用多SQL或子查询代替。
  • ②模糊查询尽量不要以%开头,如果实在要实现这个功能可以建立全文索引。
  • ③编写SQL时一定要注意字段的数据类型,否则MySQL的隐式转换会导致索引失效。
  • ④一定不要在编写SQL时让索引字段执行计算工作,尽量将计算工作放在客户端中完成。
  • ⑤对于索引字段尽量不要使用计算类函数,一定要使用时请记得将函数计算放在=后面。
  • ⑥多条件的查询SQL一定要使用联合索引中的第一个字段,否则会打破最左匹配原则。
  • ⑦对于需要对比多个字段的查询业务时,可以拆分为连表查询,使用临时表代替。
  • ⑧在SQL中不要使用反范围性的查询条件,大部分反范围性、不等性查询都会让索引失效。
  • .......

实际上无非就是根据前面给出的索引失效情况,尽量让自己编写的SQL不会导致索引失效即可,写出来的SQL能走索引查询,那就能在很大程度上提升数据检索的效率。

接下来再重点讲几个较重要的内容,既索引覆盖、索引下推、Multi-Range Read机制、索引跳跃式扫描机制。

索引覆盖

在之前聊到过,由于表中只能存在一个聚簇索引,一般都为主键索引,而建立的其他索引都为辅助索引,包括联合索引也例外,最终索引节点上存储的都是指向主键索引的值,拿前面的用户表为例:

SELECT * FROM `zz_users` WHERE `user_name`="竹子" AND `user_sex`="男";

虽然这条SQL会走联合索引查询,但是基于联合索引查询出来的值仅是一个指向主键索引的ID,然后会拿着这个ID再去主键索引中查一遍,这个过程之前聊过,被称为回表过程。

那么回表问题无法解决吗?必须得经过两次查询才能得到数据吗?答案并非如此。

比如假设此时只需要user_name、user_sex、password这三个字段的信息,此时SQL语句可以更改为如下情况:

SELECT 
    `user_name`,`user_sex`,`password`
FROM 
    `zz_users` 
WHERE 
    `user_name` = "竹子" AND `user_sex` = "男";

此时将SQL更改为查询所需的列后,就不会发生回表现象,Why?再这里很多小伙伴可能会疑惑,这是什么道理啊?因为此时所需的user_name、user_sex、password三个字段数据,在联合索引中完全包含,因此可以直接通过联合索引获取到数据。

但如果查询时用*,因为联合索引中不具备完整的一行数据,只能再次转向聚簇索引中获取完整的行数据,因此到这里大家应该也明白了为什么查询数据时,不能用*的原因,这是因为会导致索引覆盖失效,造成回表问题。

当然,再来提一点比较有意思的事情,先看SQL

EXPLAIN SELECT 
    `user_name`,`user_sex`
FROM 
    `zz_users`
WHERE 
    `password` = "1234" AND `user_sex` = "男";

比如上述这条SQL,显然是不符合联合索引的最左前缀匹配原则的,但来看看执行结果:
image-20230312205359625
这个结果是不是很令你惊讶,通过EXPLAIN分析的结果显示,这条SQL竟然使用了索引,这是什么原因呢?也是因为索引覆盖。

一句话概述:就是要查询的列,在使用的索引中已经包含,被所使用的索引覆盖,这种情况称之为索引覆盖。

索引下推

索引下推是MySQL5.6版本以后引入的一种优化机制,还是以之前的用户表为例,先来看一条SQL语句:

INSERT INTO `zz_users` VALUES(5,"竹竹","女","8888","2022-09-20 22:17:21");

SELECT * FROM `zz_users` WHERE `user_name` LIKE "竹%" AND `user_sex`="男";

首先为了更加直观的讲清楚索引下推,因此先再向用户表中增加一条数据。然后再来看看后面的查询SQL,这条SQL会使用联合索引吗?答案是会的,但只能部分使用,因为联合索引的每个节点信息大致如下:

{
    ["熊猫","女","6666"] : 1,
    ["竹子","男","1234"] : 2,
    ["子竹","男","4321"] : 3,
    ["1111","男","4321"] : 4,
    ["竹竹","女","8888"] : 5
}

由于前面使用的是模糊查询,但%在结尾,因此可以使用这个字作为条件在联合索引中查询,整个查询过程如下:

  • ①利用联合索引中的user_name字段找出「竹子、竹竹」两个索引节点。
  • ②返回索引节点存储的值「2、5」给Server层,然后去逐一做回表扫描。
  • ③在Server层中根据user_sex="男"这个条件逐条判断,最终筛选到「竹子」这条数据。

有人或许会疑惑,为什么user_sex="男"这个条件不在联合索引中处理呢?因为前面是模糊查询,所以拼接起来是这样的:竹x男,由于这个x是未知的,因此无法根据最左前缀原则去匹配数据,最终这里只能使用联合索引中user_name字段的一部分,后续的user_sex="男"还需要回到Server层处理。

那什么又叫做索引下推呢?也就是将Server层筛选数据的工作,下推到引擎层处理。

以前面的案例来讲解,MySQL5.6加入索引下推机制后,其执行过程是什么样子的呢?

  • ①利用联合索引中的user_name字段找出「竹子、竹竹」两个索引节点。
  • ②根据user_sex="男"这个条件在索引节点中逐个判断,从而得到「竹子」这个节点。
  • ③最终将「竹子」这个节点对应的「2」返回给Server层,然后聚簇索引中回表拿数据。

相较于没有索引下推之前,原本需要做「2、5」两次回表查询,但在拥有索引下推之后,仅需做「2」一次回表查询。

索引下推在MySQL5.6版本之后是默认开启的,可以通过命令set optimizer_switch='index_condition_pushdown=off|on';命令来手动管理。

MRR(Multi-Range Read)机制

Multi-Range Read简称为MRR机制,这也是和索引下推一同在MySQL5.6版本中引入的性能优化措施,那什么叫做MRR优化呢?

一般来说,在实际业务中我们应当尽量通过索引覆盖的特性,减少回表操作以降低IO次数,但在很多时候往往又不得不做回表才能查询到数据,但回表显然会导致产生大量磁盘IO,同时更严重的一点是:还会产生大量的离散IO,下面举个例子来理解。

SELECT * FROM `zz_student_score` WHERE `score` BETWEEN 0 AND 59;

上述这条SQL所做的工作很简单,就是在学生成绩表中查询所有成绩未及格的学生信息,假设成绩字段上存在一个普通索引,那思考一下,这条SQL的执行流程是什么样的呢?

  • ①先在成绩字段的索引上找到0分的节点,然后拿着ID去回表得到成绩零分的学生信息。
  • ②再次回到成绩索引,继续找到所有1分的节点,继续回表得到1分的学生信息。
  • ③再次回到成绩索引,继续找到所有2分的节点…
  • ④周而复始,不断重复这个过程,直到将0~59分的所有学生信息全部拿到为止。

那此时假设此时成绩0~5分的表数据,位于磁盘空间的page_01页上,而成绩为5~10分的数据,位于磁盘空间的page_02页上,成绩为10~15分的数据,又位于磁盘空间的page_01页上。此时回表查询时就会导致在page_01、page_02两页空间上来回切换,但0~5、10~15分的数据完全可以合并,然后读一次page_01就可以了,既能减少IO次数,同时还避免了离散IO

MRR机制就主要是解决这个问题的,针对于辅助索引的回表查询,减少离散IO,并且将随机IO转换为顺序IO,从而提高查询效率。

MRR机制具体是怎么做的呢?MRR机制中,对于辅助索引中查询出的ID,会将其放到缓冲区的read_rnd_buffer中,然后等全部的索引检索工作完成后,或者缓冲区中的数据达到read_rnd_buffer_size大小时,此时MySQL会对缓冲区中的数据排序,从而得到一个有序的ID集合:rest_sort,最终再根据顺序IO去聚簇/主键索引中回表查询数据。

SET @@optimizer_switch='mrr=on|off,mrr_cost_based=on|off';

可以通过上述这条命令开启或关闭MRR机制,MySQL5.6及以后的版本是默认开启的。

Index Skip Scan索引跳跃式扫描

在讲联合索引时,咱们提到过最左前缀匹配原则,也就是SQL的查询条件中必须要包含联合索引的第一个字段,这样才能命中联合索引查询,但实际上这条规则也并不是100%遵循的。因为在MySQL8.x版本中加入了一个新的优化机制,也就是索引跳跃式扫描,这种机制使得咱们即使查询条件中,没有使用联合索引的第一个字段,也依旧可以使用联合索引,看起来就像跳过了联合索引中的第一个字段一样,这也是跳跃扫描的名称由来。

但跳跃扫描究竟是怎么实现的呢?上个栗子快速理解一下。

比如此时通过(A、B、C)三个列建立了一个联合索引,此时有如下一条SQL

SELECT * FROM `tb_xx` WHERE B = `xxx` AND C = `xxx`;

按理来说,这条SQL既不符合最左前缀原则,也不具备使用索引覆盖的条件,因此绝对是不会走联合索引查询的,但思考一个问题,这条SQL中都已经使用了联合索引中的两个字段,结果还不能使用索引,这似乎有点亏啊对不?因此MySQL8.x推出了跳跃扫描机制,但跳跃扫描并不是真正的“跳过了”第一个字段,而是优化器为你重构了SQL,比如上述这条SQL则会重构成如下情况:

SELECT * FROM `tb_xx` WHERE B = `xxx` AND C = `xxx`
UNION ALL
SELECT * FROM `tb_xx` WHERE B = `xxx` AND C = `xxx` AND A = "yyy"
......
SELECT * FROM `tb_xx` WHERE B = `xxx` AND C = `xxx` AND A = "zzz";

其实也就是MySQL优化器会自动对联合索引中的第一个字段的值去重,然后基于去重后的值全部拼接起来查一遍,一句话来概述就是:虽然你没用第一个字段,但我给你加上去,今天这个联合索引你就得用,不用也得给我用

但是跳跃扫描机制也有很多限制,比如多表联查时无法触发、SQL条件中有分组操作也无法触发、SQL中用了DISTINCT去重也无法触发…,总之有很多限制条件,具体的可以参考《MySQL官网8.0-跳跃扫描》

其实这个跳跃性扫描机制,只有在唯一性较差的情况下,才能发挥出不错的效果,如果你联合索引的第一个字段,是一个值具备唯一性的字段,那去重一次再拼接,几乎就等价于走一次全表。

最后,可以通过通过set @@optimizer_switch = 'skip_scan=off|on';命令来选择开启或关闭跳跃式扫描机制。当然,该参数仅限MySQL8.0以上的版本,如果在此之下的版本暂时就不用考虑了。

<think>好的,我现在需要帮助用户检查MySQL是否启用了索引条件下推(Index Condition Pushdown,ICP)的功能。首先,我得确认自己对ICP的理解是否正确。ICP是MySQL 5.6引入的一个优化功能,它允许在存储引擎层应用WHERE子句中的某些条件,从而减少需要返回到服务器层的数据行数,提升查询性能。特别是当部分条件可以利用索引但无法完全过滤时,ICP能有效减少回表次数。 接下来,用户的问题是如何检查ICP是否启用。根据之前的知识,MySQL的系统变量通常可以通过SHOW VARIABLES或查询performance_schema来获取。我需要确定控制ICP的变量名称。记得ICP相关的变量是optimizer_switch中的一个选项,而optimizer_switch包含了多个优化器的开关,每个开关用逗号分隔。 因此,首先应该检查optimizer_switch变量的值,确认index_condition_pushdown是否启用。用户可能需要执行类似`SHOW VARIABLES LIKE 'optimizer_switch';`的命令,或者在会话级别动态修改这个变量。此外,也可以通过查询performance_schema的session_variables表来获取当前设置。 另外,考虑到用户可能不太熟悉如何解析optimizer_switch的值,我需要详细解释输出的格式,比如找到index_condition_pushdown=on这样的部分。同时,还要提醒用户,这个设置可以是全局的或会话级的,检查时需要注意当前会话的配置。 还需要考虑不同MySQL版本的差异。ICP在5.6及以后版本才支持,如果用户使用的是旧版本,可能不支持该功能。因此,可能需要建议用户先确认版本信息,使用`SELECT VERSION();`来查看。 此外,引用中提到的参考资料[^1][^2][^4][^5]都涉及到ICP的工作原理和优化效果,可以引用这些内容来说明启用ICP的重要性,以及如何通过系统变量进行控制。例如,引用[^4]提到在没有ICP时,查询必须检索所有符合zipcode条件的行,而启用ICP后可以在索引层进行更多过滤。 最后,用户可能需要临时启用或禁用ICP来进行测试,这时可以指导他们使用SET命令修改optimizer_switch,例如`SET SESSION optimizer_switch='index_condition_pushdown=on';`,如引用[^3]中提到的设置sort_buffer_size的方法类似。 总结步骤应该是:1. 检查MySQL版本是否支持ICP;2. 查看optimizer_switch中的index_condition_pushdown状态;3. 解释如何根据需要开启或关闭该功能。同时,提供示例命令和输出解释,确保用户能够理解和操作。</think>要检查MySQL是否启用了索引下推(Index Condition Pushdown, ICP),可以通过以下步骤操作: --- ### **1. 查看`optimizer_switch`系统变量** ICP功能由`optimizer_switch`参数控制,需检查其包含的`index_condition_pushdown`选项状态。 执行以下SQL命令: ```sql SHOW VARIABLES LIKE 'optimizer_switch'; ``` 或更精确地提取相关值: ```sql SELECT @@optimizer_switch; ``` **输出示例**: ``` index_condition_pushdown=on, ..., mrr=on ``` - 若输出中包含`index_condition_pushdown=on`,表示ICP已启用[^2]。 --- ### **2. 通过`performance_schema`查询(可选)** 若需编程式获取状态,可查询`performance_schema`: ```sql SELECT VARIABLE_VALUE FROM performance_schema.session_variables WHERE VARIABLE_NAME = 'optimizer_switch'; ``` 解析返回的字符串中是否包含`index_condition_pushdown=on`。 --- ### **3. 临时启用/禁用ICP** 若需测试ICP效果,可在会话级别动态调整: - **启用ICP**: ```sql SET SESSION optimizer_switch = 'index_condition_pushdown=on'; ``` - **禁用ICP**: ```sql SET SESSION optimizer_switch = 'index_condition_pushdown=off'; ``` --- ### **4. 验证ICP效果** 通过执行计划(`EXPLAIN`)观察是否使用ICP: ```sql EXPLAIN SELECT * FROM table WHERE indexed_column = 'value' AND non_indexed_column = 'value'; ``` - 若`Extra`列显示`Using index condition`,则ICP生效。 --- ### **注意事项** - **版本要求**:ICP仅支持MySQL 5.6及以上版本,可使用`SELECT VERSION();`确认版本[^5]。 - **全局与会话设置**:若需全局生效,需在配置文件中修改`optimizer_switch`并重启服务。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

小颜-

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

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

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

打赏作者

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

抵扣说明:

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

余额充值