记一次Mysql索引失效原因分析

前言

此文章包含Mysql的Where条件查询执行过程、范围查询使联合索引停止匹配、回表操作分析、常见索引失效场景、Extra分析等知识。

背景

6千万数据量的数据表出现了一个满查询,复现sql语句发现查询并没有走索引而是走全表查询,找出索引失效原因。

# sql语句EXPLAIN SELECT count(*) FROM order_recipient_extend_tab WHERE start_date>'1628442000' and start_date<'1631120399' and station_id='1809' and status='2';

order_recipient_extend_tab 表有6千万数据,慢查询的查询字段包括 start_date、station_id、status,按照索引设计初衷会走但实际上失效的索引是:

联合索引

字段1

字段2

字段3

idx_date_station_driver

start_date

station_id

driver_id

Where条件查询执行过程

了解Mysql怎么执行where条件查询,能更快速清晰地洞见索引失效的原因。此次慢查询中匹配度高的索引是idx_date_station_driver,分析此次慢查询中where条件查询的执行过程。

Mysql对where条件提取规则主要可以归纳为三大类:Index Key (First Key & Last Key),Index FilterTable Filter

Index Key

Index Key用于确定此次sql查询在索引树上的范围。一个范围包括起始和终止,Index First Key用于定位索引查询的起始范围,Index Last Key用于定位索引查询的终止范围

  • Index First Key 提取规则:从索引的第一个字段开始,检查该字段在where条件中是否存在,若存在且条件是=、>=,则将对应的条件加入Index First Key之中,继续读取索引的下一个字段;若存在且条件是>,则将对应的条件加入Index First Key中,然后终止Index First Key的提取;若不存在,也终止Index First Key的提取。
  • Index Last Key 与Index First Key正好相反,提取规则:从索引的第一个字段开始,检查其在where条件中是否存在,若存在并且条件是=、<=,则将对应条件加入到Index Last Key中,继续提取索引的下一个字段;若存在并且条件是 < ,则将条件加入到Index Last Key中,然后终止提取;若不存在,也终止Index Last Key的提取。

按照Index Key的提取规则,在此次慢查询中提取出来的Index Last Key为:start_date>'1628442000',Index Last Key为: start_date<'1631120399'。

Index First Key只是用来定位索引的起始范围,使用Index First Key条件,从索引B 树的根节点开始,使用二分搜索方法快速索引到正确的叶节点位置。Where查询过程中Index First Key只做了一次判断。

Index Last Key,用来定位索引的终止范围,因此对于起始范围之后读到的每一条索引记录,均需要判断是否已经超过了Index Last Key的范围,若超过,则当前查询结束。

Index Filter

在Index Key确定的索引范围中,并不是所有的索引记录都满足查询条件。比如Index Last Key和Index Last Key范围中,不是所有索引记录都满足 station_id = '1809'。这个时候就需要用到Index Filter了。

Index Filter,又名索引下推用于过滤索引查询范围中不满足查询条件的记录。对于索引范围中的每一条记录,均需要与Index Filter进行对比,若不满足Index Filter则直接丢弃,继续读取索引下一条记录。

Index Filter的提取规则:从索引的第一个字段开始,检查其在where条件中是否存在,若存在且条件仅为 =,则跳过第一字段继续检查索引下一字段,下一索引列采取相同的提取规则(解释:条件为=的字段已经在Index Key中过滤掉了);若存在且条件为 >=、>、<、<= 其中的几种,则跳过当前索引字段,将其余where条件中索引相关字段全部加入到Index Filter之中。

按照Index Filter的提取规则,在此次慢查询中提取出来的Index Filter为:station_id='1809'。在Index Key确定的索引查询范围中,遍历索引记录时都需要比较 station_id='1809',不满足该条件则直接丢失,继续读取索引下一条记录。

Table Filter

Table Filter用于过滤掉索引无法过滤的数据。在二级索引中通过主键回表查询到整行记录后,判断该记录是否符合Table Filter条件,不符合则丢失,继续判断下一条记录。

提取规则很简单:所有不属于索引字段的查询条件,均归为Table Filter之中。按照Table Filter的提取规则,在此次查询中Table Filter为:status='2’。

总结和补充

Index Key用于确定索引扫描的范围;Index Filter用于在索引中进行过滤;Table Filter需要回表后在Mysql服务器进行过滤。

Index Key和Index Filter发生在InnoDB存储层,Table Filter发生在Mysql Server层。

在 MySQL5.6 之前,并不区分Index Filter与Table Filter,统统将Index First Key与Index Last Key范围内的索引记录,回表读取完整记录,然后返回给MySQL Server层进行过滤。

在MySQL 5.6及之后,Index Filter与Table Filter分离,Index Filter下降到InnoDB的存储引擎层进行过滤,减少了回表与返回MySQL Server层的记录交互开销,提高了SQL的执行效率。

分析索引失效原因

首先是count(),此时通配符 * 经优化并不会拓展所有列,实际上会忽略所有的列直接统计行数。所以只想收集行数最好使用count()。

接下来分析where语句。假设此慢查询会使用了二级索引idx_date_station_driver,按照上面where条件查询的执行过程,该慢查询的Index First Key为start_date>'1628442000',Index Last Key为: start_date<'1631120399',Index Filter为:station_id='1809',Table Filter为:status='2’。

提取Index First Key后在索引B 树上定位索引起始范围就是索引匹配的过程,在索引B 树上使用二分搜索方法快速定位符合查询条件的起始叶子节点。通过上文Where条件查询执行过程,我们知道该慢查询的where条件(start_date>'1628442000' and start_date<'1631120399' and status='2' and station_id='1809'),只匹配了索引idx_date_station_driver(start_date, station_id, driver_id)的第一个字段,即只匹配了idx_date_station_driver(start_date),station_id='1809'精确查询并没有作用到匹配索引上,而是在Index Filter即索引下推过程中发挥了作用。实际上这里是因为范围查询使联合索引停止匹配

范围查询导致联合索引停止匹配

为什么范围查询会使联合索引停止匹配?这里涉及到最左前缀匹配原理。假设建立一个联合索引 index(a, b),会先对a进行排序,在a相等的情况下对b进行排序,如下图所示。在该索引树上,a是全局有序的,而b则处于全局无序、局部有序状态。从全局来看,b的值为1、2、1、4、1、2,只有 b=2 查询条件无法直接使用该索引;从局部来看,当a的值确定时,b则是有序状态,a=2 && b=4可以使用该索引。所以范围查询使联合索引停止匹配的根本原因是,索引树上非首字段的有序状态依赖前一个字段相等情况,而范围查询破坏了下一个索引字段局部有序状态,导致索引停止匹配。

范围查询使联合索引停止匹配,并不能在索引匹配的时候就过滤掉 station_id不等于'1809' 的数据,导致Mysql在索引上的扫描范围Index First Key和Index Last Key完全由start_timestamp_of_date时间决定。start_timestamp_of_date范围查询可以过滤73%数据量,而station_id='1809'精确查询能过滤掉99%的数据量。

查询条件

数据量

占比

所有数据

6367万

100%

start_timestamp_of_date>'1628442000' and start_timestamp_of_date<'1631120399'

1742万

27.35%

station_id='1809'

8万

0.16%

回表操作的开销

由于status字段不在索引idx_date_station_driver字段上,所以需要回表查询索引过滤的数据,在Mysql服务层判数据是否符合查询条件。

Mysql的优化器在执行sql语句时会先估算走匹配度高的索引的开销,如果走索引的开销比查全表还大,那么Mysql会选择全表扫描。这个结论可能反常识,在我们印象中索引就是用来提高查询效率的。这里主要涉及两个因素:

  1. 当查询条件或查找的字段不在二级索引的字段上时,会执行回表操作,会走:二级索引 主键索引。
  2. 磁盘随机I/O的性能低于顺序I/O。回表查询在主键索引上是随机I/O,全表扫描在主键索引上是顺序I/O。

做实验分析回表操作的开销是否是索引失效的直接原因?

去除status='0'查询条件,explain查看该查询是否使用到了索引idx_date_station_driver。结果如下图所示,少了回表操作的开销,索引并未失效。

总结

结合以上分析总结索引失效原因是:范围查询使联合索引停止匹配,索引匹配过滤的数据不够多,导致Mysql优化器估算出Table Filter的回表操作开销大于全表查询,所以选择了全表查询。范围查询使联合索引停止匹配是索引失效的罪魁祸首,而回表操作的开销是索引失效的直接原因。

优化索引

该慢查询索引失效的罪魁祸首是范围查询使联合索引停止匹配,只需要把范围查询的字段调整到精确查询的字段后面,即将

联合索引 idx_date_station_driver(start_date, station_id, driver_id) 修改为 idx_station_date_driver(station_id, start_date, driver_id) 。优化后的结果如下图所示。

拓展

索引失效常见场景

  1. 违反最左前缀匹配原则。例如有索引index(a,b),但查询条件只有b字段。
  2. 在索引列上做任何操作,包括计算、函数、类型转换等。
  3. 范围查询使联合索引停止匹配。
  4. 减少select*的使用。避免不必要的回表操作开销,尽量使用覆盖索引。
  5. 使用不等于(!=、<>),使用or操作。
  6. 字符串不加单引号索引失效。
  7. like以通配符开头'%abc'。注意like 'abc%’ 是可以走索引的。
  8. order by 违反最左匹配原则,含非索引字段排序,会产生文件排序。
  9. group by 违反最左匹配原则,含非索引字段分组,会导致产生临时表。

Explain分析

慢查询的分析离不开mysql的explain语句,explain主要关注两个字段Type和Extra。

Type表示访问数据的方式,Extra表示过滤和整理数据的方式。这里列举出来方便查找。

Type

Extra

ALL

全表扫描

Using index

使用覆盖索引,不需要回表,不需要Mysql服务层过滤

index

索引树全扫描

Using where

从存储引擎层获取数据,在Mysql服务层用where查询条件过滤数据。

range

索引树范围扫描

Using where; Using index

索引范围扫描。索引扫描和全表扫描类似,只是发生的层面不一样。

ref

非唯一性索引扫描,比如非唯一索引和唯一索引的非唯一前缀

Using index condition

使用索引下推,在存储引擎层充分利用查询索引字段过滤数据

eq_ref

唯一性索引扫描,比如唯一索引、主键索引

Using temporary

临时表存储结果,用于排序和分组查询

const

将查询转化成常量

Using filesort

文件排序,用于排序

NULL

不用访问表或索引

NULL

回表

(0)

相关推荐

  • mysql索引命中规则

    最左匹配原则  1.先定位该sql的查询条件,有哪些,那些是等值的,那些是范围的条件.  2.等值的条件去命中索引最左边的一个字段,然后依次从左往右命中,范围的放在最后.   分析讲解  1.mysq ...

  • 索引介绍

    一.索引介绍 1.什么是索引 1)索引就好比一本书的目录,它能让你更快的找到自己想要的内容. 2)让获取的数据更有目的性,从而提高数据库检索数据的性能. 2.索引类型介绍 1)BTREE:B+树索引 ...

  • 95%的人都不知道 MySQL还有索引管理与执行计划

    1.1 索引的介绍 索引是对数据库表中一列或多列的值进行排序的一种结构,使用索引可快速访问数据库表中的特定信息.如果想按特定职员的姓来查找他或她,则与在表中搜索所有的行相比,索引有助于更快地获取信息. ...

  • 一条 sql 的执行过程详解

    重磅干货,第一时间送达 写操作执行过程 如果这条sql是写操作(insert.update.delete),那么大致的过程如下,其中引擎层是属于 InnoDB 存储引擎的,因为InnoDB 是默认的存 ...

  • MySQL索引失效底层原理分析,MySQL索引为什么失效,这篇文章全部告诉你

    前言 吊打面试官又来啦,今天我们讲讲MySQL索引为什么会失效,很多文章和培训机构的教程,都只会告诉你,在什么情况下索引会失效. 比如:没遵循最佳左前缀法则.范围查询的右边会失效.like查询用不到索 ...

  • 案例分享:自锁螺母振动失效原因分析及预防

    版权信息 作者:徐家强,付建建等 单位:河南航天精工制造有限公司研究所.河南省紧固连接技术重点实验室 来源:<金属加工(热加工)>杂志 在航空发动机的设计.制造.使用及维护过程中,螺纹联接 ...

  • 干货分享│止回阀失效事故分析与解决对策,遇到问题拿出来直接找原因!

    2018年3月12日,A炼油厂柴油加氢装置加氢进料泵联锁停泵后,因泵出口两道止回阀失效,导致系统内高压介质(柴油.氢气,5.07MPa)从泵出口经泵体反串入原料罐(设计压力0.38MPa),致使原料罐 ...

  • 常见滚动轴承的失效形式及原因分析

    滚动轴承可以有效地减少轴承各零部件之间的摩擦,从而更加流畅地运转,可以有效帮助提高机械设备的使用性能.但滚动轴承在长时间使用后有时会出现失效的现象,那么,大家知道常见滚动轴承的失效形式及原因具体都有哪 ...

  • 机械密封从失效形式分析故障原因

    每 天 13:20,干 货 知 识 与 你 准 时 相 约  ! 一 起 来! 每 天 进 步 一 点 点,加 油 ! 通过对失效原因的分析,可以提高应用机械密封的技术水平.结构设计上的改进,在很大程 ...

  • 钻机液压缸活塞杆失效原因及防止措施(2)分析

    液压缸对于整个钻机来说,处于举足轻重的地位.它的结构是否合理.密封是否良好,对于钻机的参数及性能均有重大影响.导致密封失效有以下几种: 1  流体动压中拖拽压力 特定类型的密封的性能由密封界面间隙的流 ...

  • 液压阀失效原因之二分析:液压卡紧

    在上文我们已经分析过机械性失效的原因,在本文我们将对液压卡紧的原因进行系统性的分析. 1.导致液压卡紧的原因 压力油液流经液压阀圆柱形滑阀结构时,作用在阀芯上的径向不平衡力使阀芯卡住,称为" ...

  • 刀具破损、磨损、崩刃怎么办?从根本上分析刀具失效原因

    制造交流圈 圈主:科技智能制造 123成员 进入圈子 刀具破损.磨损.崩刃怎么办?从根本上分析刀具失效原因,附有解决方案,快来了解一下! 刀具破损的表现 1) 切削刃微崩 当工件材料组织.硬度.余量不 ...

  • 壳管式换热器管束失效的原因分析

    一.换热器发生爆炸的原因分析 1.自制换热器,盲目将换热器结构和材质做较大改动,制造质量差,不符合压力容器规范,设备强度大大降低. 2.换热器焊接质量差,特别是焊接接头处未焊透,又未进行焊缝探伤检查. ...