MySQL中索引失效的场景

news2024/11/17 17:28:06

1.对索引使用左或者左右模糊匹配

当我们使用左或者左右模糊匹配的时候,也就是 like %xx 或者 like %xx% 这两种方式都会造成索引失效。

比如下面的 like 语句,查询 name 后缀为「林」的用户,执行计划中的 type=ALL 就代表了全表扫描,而没有走索引。

// name 字段为二级索引
select * from t_user where name like '%林';

如果是查询 name 前缀为林的用户,那么就会走索引扫描,执行计划中的 type=range 表示走索引扫描,key=index_name 看到实际走了 index_name 索引:

// name 字段为二级索引
select * from t_user where name like '林%';

为什么 like 关键字左或者左右模糊匹配无法走索引呢?

因为索引 B+ 树是按照「索引值」有序排列存储的,只能根据前缀进行比较。

举个例子,下面这张二级索引图(图中叶子节点之间我画了单向链表,但是实际上是双向链表,原图我找不到了,修改不了,偷个懒我不重画了,大家脑补成双向链表就行),是以 name 字段有序排列存储的。

假设我们要查询 name 字段前缀为「林」的数据,也就是 name like '林%',扫描索引的过程:

  • 首节点查询比较:林这个字的拼音大小比首节点的第一个索引值中的陈字大,但是比首节点的第二个索引值中的周字小,所以选择去节点2继续查询;
  • 节点 2 查询比较:节点2的第一个索引值中的陈字的拼音大小比林字小,所以继续看下一个索引值,发现节点2有与林字前缀匹配的索引值,于是就往叶子节点查询,即叶子节点4;
  • 节点 4 查询比较:节点4的第一个索引值的前缀符合林字,于是就读取该行数据,接着继续往右匹配,直到匹配不到前缀为林的索引值。

如果使用 name like '%林' 方式来查询,因为查询的结果可能是「陈林、张林、周林」等之类的,所以不知道从哪个索引值开始比较,于是就只能通过全表扫描的方式来查询。

2.对索引使用函数

有时候我们会用一些 MySQL 自带的函数来得到我们想要的结果,这时候要注意了,如果查询条件中对索引字段使用函数,就会导致索引失效。

比如下面这条语句查询条件中对 name 字段使用了 LENGTH 函数,执行计划中的 type=ALL,代表了全表扫描:

// name 为二级索引
select * from t_user where length(name)=6;

为什么对索引使用函数,就无法走索引了呢?

因为索引保存的是索引字段的原始值,而不是经过函数计算后的值,自然就没办法走索引了。

不过,从 MySQL 8.0 开始,索引特性增加了函数索引,即可以针对函数计算后的值建立一个索引,也就是说该索引的值是函数计算后的值,所以就可以通过扫描索引来查询数据。

举个例子,我通过下面这条语句,对 length(name) 的计算结果建立一个名为 idx_name_length 的索引。

alter table t_user add key idx_name_length ((length(name)));

然后我再用下面这条查询语句,这时候就会走索引了。

3.对索引进行表达式计算

在查询条件中对索引进行表达式计算,也是无法走索引的。

比如,下面这条查询语句,执行计划中 type = ALL,说明是通过全表扫描的方式查询数据的:

explain select * from t_user where id + 1 = 10;

但是,如果把查询语句的条件改成 where id = 10 - 1,这样就不是在索引字段进行表达式计算了,于是就可以走索引查询了。

为什么对索引进行表达式计算,就无法走索引了呢?

原因跟对索引使用函数差不多。

因为索引保存的是索引字段的原始值,而不是 id + 1 表达式计算后的值,所以无法走索引,只能通过把索引字段的取值都取出来,然后依次进行表达式的计算来进行条件判断,因此采用的就是全表扫描的方式。

有的同学可能会说,这种对索引进行简单的表达式计算,在代码特殊处理下,应该是可以做到索引扫描的,比方将 id + 1 = 10 变成 id = 10 - 1。

是的,是能够实现,但是 MySQL 还是偷了这个懒,没有实现。

我的想法是,可能也是因为,表达式计算的情况多种多样,每种都要考虑的话,代码可能会很臃肿,所以干脆将这种索引失效的场景告诉程序员,让程序员自己保证在查询条件中不要对索引进行表达式计算。

4. 对索引隐式类型转换

如果索引字段是字符串类型,但是在条件查询中,输入的参数是整型的话,你会在执行计划的结果发现这条语句会走全表扫描。

我在原本的 t_user 表增加了 phone 字段,是二级索引且类型是 varchar。

然后我在条件查询中,用整型作为输入参数,此时执行计划中 type = ALL,所以是通过全表扫描来查询数据的。

select * from t_user where phone = 1300000001;

但是如果索引字段是整型类型,查询条件中的输入参数即使字符串,是不会导致索引失效,还是可以走索引扫描。

我们再看第二个例子,id 是整型,但是下面这条语句还是走了索引扫描的。

 explain select * from t_user where id = '1';

为什么第一个例子会导致索引失效,而第二例子不会呢?

要明白这个原因,首先我们要知道 MySQL 的数据类型转换规则是什么?就是看 MySQL 是会将字符串转成数字处理,还是将数字转换成字符串处理。

我在看《mysql45讲的时候》看到一个简单的测试方式,就是通过 select “10” > 9 的结果来知道MySQL 的数据类型转换规则是什么:

  • 如果规则是 MySQL 会将自动「字符串」转换成「数字」,就相当于 select 10 > 9,这个就是数字比较,所以结果应该是 1;
  • 如果规则是 MySQL 会将自动「数字」转换成「字符串」,就相当于 select "10" > "9",这个是字符串比较,字符串比较大小是逐位从高位到低位逐个比较(按ascii码) ,那么"10"字符串相当于 “1”和“0”字符的组合,所以先是拿 “1” 字符和 “9” 字符比较,因为 “1” 字符比 “9” 字符小,所以结果应该是 0。

在 MySQL 中,执行的结果如下图:

上面的结果为 1,说明 MySQL 在遇到字符串和数字比较的时候,会自动把字符串转为数字,然后再进行比较

前面的例子一中的查询语句,我也跟大家说了是会走全表扫描:

//例子一的查询语句
select * from t_user where phone = 1300000001;

这是因为 phone 字段为字符串,所以 MySQL 要会自动把字符串转为数字,所以这条语句相当于:

select * from t_user where CAST(phone AS signed int) = 1300000001;

可以看到,CAST 函数是作用在了 phone 字段,而 phone 字段是索引,也就是对索引使用了函数!而前面我们也说了,对索引使用函数是会导致索引失效的

例子二中的查询语句,我跟大家说了是会走索引扫描:

//例子二的查询语句
select * from t_user where id = "1";

这时因为字符串部分是输入参数,也就需要将字符串转为数字,所以这条语句相当于:

select * from t_user where id = CAST("1" AS signed int);

可以看到,索引字段并没有用任何函数,CAST 函数是用在了输入参数,因此是可以走索引扫描的。

5. 联合索引非最左匹配

对主键字段建立的索引叫做聚簇索引,对普通字段建立的索引叫做二级索引。

那么多个普通字段组合在一起创建的索引就叫做联合索引,也叫组合索引。

创建联合索引时,我们需要注意创建时的顺序问题,因为联合索引 (a, b, c) 和 (c, b, a) 在使用的时候会存在差别。

联合索引要能正确使用需要遵循最左匹配原则,也就是按照最左优先的方式进行索引的匹配。

比如,如果创建了一个 (a, b, c) 联合索引,如果查询条件是以下这几种,就可以匹配上联合索引:

  • where a=1;
  • where a=1 and b=2 and c=3;
  • where a=1 and b=2;

需要注意的是,因为有查询优化器,所以 a 字段在 where 子句的顺序并不重要。

但是,如果查询条件是以下这几种,因为不符合最左匹配原则,所以就无法匹配上联合索引,联合索引就会失效:

  • where b=2;
  • where c=3;
  • where b=2 and c=3;

有一个比较特殊的查询条件:where a = 1 and c = 3 ,符合最左匹配吗?

这种其实严格意义上来说是属于索引截断,不同版本处理方式也不一样。

MySQL 5.5 的话,前面 a 会走索引,在联合索引找到主键值后,开始回表,到主键索引读取数据行,Server 层从存储引擎层获取到数据行后,然后在 Server 层再比对 c 字段的值。

从 MySQL 5.6 之后,有一个索引下推功能,可以在存储引擎层进行索引遍历过程中,对索引中包含的字段先做判断,直接过滤掉不满足条件的记录,再返还给 Server 层,从而减少回表次数。

索引下推的大概原理是:截断的字段不会在 Server 层进行条件判断,而是会被下推到「存储引擎层」进行条件判断(因为 c 字段的值是在 (a, b, c) 联合索引里的),然后过滤出符合条件的数据后再返回给 Server 层。由于在引擎层就过滤掉大量的数据,无需再回表读取数据来进行判断,减少回表次数,从而提升了性能。

比如下面这条 where a = 1 and c = 0 语句,我们可以从执行计划中的 Extra=Using index condition 使用了索引下推功能。

为什么联合索引不遵循最左匹配原则就会失效?

原因是,在联合索引的情况下,数据是按照索引第一列排序,第一列数据相同时才会按照第二列排序。

也就是说,如果我们想使用联合索引中尽可能多的列,查询条件中的各个列必须是联合索引中从最左边开始连续的列。如果我们仅仅按照第二列搜索,肯定无法走索引。

6.WHERE 子句中的 OR

在 WHERE 子句中,如果在 OR 前的条件列是索引列,而在 OR 后的条件列不是索引列,那么索引会失效。

举个例子,比如下面的查询语句,id 是主键,age 是普通列,从执行计划的结果看,是走了全表扫描。

select * from t_user where id = 1 or age = 18;

这是因为 OR 的含义就是两个只要满足一个即可,因此只有一个条件列是索引列是没有意义的,只要有条件列不是索引列,就会进行全表扫描。

要解决办法很简单,将 age 字段设置为索引即可。

可以看到 type=index merge, index merge 的意思就是对 id 和 age 分别进行了扫描,然后将这两个结果集进行了合并,这样做的好处就是避免了全表扫描。

总结

今天给大家介绍了 6 种会发生索引失效的情况:

  • 当我们使用左或者左右模糊匹配的时候,也就是 like %xx 或者 like %xx%这两种方式都会造成索引失效;
  • 当我们在查询条件中对索引列使用函数,就会导致索引失效。
  • 当我们在查询条件中对索引列进行表达式计算,也是无法走索引的。
  • MySQL 在遇到字符串和数字比较的时候,会自动把字符串转为数字,然后再进行比较。如果字符串是索引列,而条件语句中的输入参数是数字的话,那么索引列会发生隐式类型转换,由于隐式类型转换是通过 CAST 函数实现的,等同于对索引列使用了函数,所以就会导致索引失效。
  • 联合索引要能正确使用需要遵循最左匹配原则,也就是按照最左优先的方式进行索引的匹配,否则就会导致索引失效。
  • 在 WHERE 子句中,如果在 OR 前的条件列是索引列,而在 OR 后的条件列不是索引列,那么索引会失效。

MySQL 使用 like “%x“,索引一定会失效吗?

题目一

题目一很简单,相信大家都能分析出答案,我昨天分享的索引失效文章里也提及过。

**「题目 1 」**的数据库表如下,id 是主键索引,name 是二级索引,其他字段都是非索引字段。

这四条模糊匹配的查询语句,第一条和第二条都会走索引扫描,而且都是选择扫描二级索引(index_name),我贴个第二条查询语句的执行计划结果图:

而第三和第四条会发生索引失效,执行计划的结果 type= ALL,代表了全表扫描。

题目二

题目 2 的数据库表特别之处在于,只有两个字段,一个是主键索引 id,另外一个是二级索引 name。

针对题目 2 的数据表,第一条和第二条模糊查询语句也是一样可以走索引扫描,第二条查询语句的执行计划如下,Extra 里的 Using index 说明用上了覆盖索引:

我们来看一下第三条查询语句的执行计划(第四条也是一样的结果):

从执行计划的结果中,可以看到 key=index_name,也就是说用上了二级索引,而且从 Extra 里的 Using index 说明用上了覆盖索引。

这是为什么呢?

首先,这张表的字段没有「非索引」字段,所以 select * 相当于 select id,name,然后这个查询的数据都在二级索引的 B+ 树,因为二级索引的 B+ 树的叶子节点包含「索引值+主键值」,所以查二级索引的 B+ 树就能查到全部结果了,这个就是覆盖索引。

但是执行计划里的 type 是 index,这代表着是通过全扫描二级索引的 B+ 树的方式查询到数据的,也就是遍历了整颗索引树。

而第一和第二条查询语句的执行计划中 type 是 range,表示对索引列进行范围查询,也就是利用了索引树的有序性的特点,通过查询比较的方式,快速定位到了数据行。

所以,type=range 的查询效率会比 type=index 的高一些。

为什么选择全扫描二级索引树,而不扫描聚簇索引树呢?

因为二级索引树的记录东西很少,就只有「索引列+主键值」,而聚簇索引记录的东西会更多,比如聚簇索引中的叶子节点则记录了主键值、事务 id、用于事务和 MVCC 的回滚指针以及所有的剩余列。

再加上,这个 select * 不用执行回表操作。

所以, MySQL 优化器认为直接遍历二级索引树要比遍历聚簇索引树的成本要小的多,因此 MySQL 选择了「全扫描二级索引树」的方式查询数据。

为什么这个数据表加了非索引字段,执行同样的查询语句后,怎么变成走的是全表扫描呢?

加了其他字段后,select * from t_user where name like "%xx"; 要查询的数据就不能只在二级索引树里找了,得需要回表操作才能完成查询的工作,再加上是左模糊匹配,无法利用索引树的有序性来快速定位数据,所以得在二级索引树逐一遍历,获取主键值后,再到聚簇索引树检索到对应的数据行,这样实在太累了。

所以,优化器认为上面这样的查询过程的成本实在太高了,所以直接选择全表扫描的方式来查询数据。


从这个思考题我们知道了,使用左模糊匹配(like "%xx")并不一定会走全表扫描,关键还是看数据表中的字段。

如果数据库表中的字段只有主键+二级索引,那么即使使用了左模糊匹配,也不会走全表扫描(type=all),而是走全扫描二级索引树(type=index)。

再说一个相似,我们都知道联合索引要遵循最左匹配才能走索引,但是如果数据库表中的字段都是索引的话,即使查询过程中,没有遵循最左匹配原则,也是走全扫描二级索引树(type=index),比如下图:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/634329.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

什么是SOME/IP-SD?

SOME/IP-SD 是"Scalable service-Oriented MiddlewarE over IP - Service Discovery"的缩写,是SOME/IP的一种特殊报文,可以让Client知道Server可以提供哪些服务,SOME/IP有两种动态发现服务的机制:一种是Offer Service&a…

Javascript作用域 (局部作用域和全局作用域) 详细介绍

Javascript作用域 (局部作用域和全局作用域) 详细介绍 作用域是当前的执行上下文,值和表达式在其中“可见”或可被访问。 常见的作用域为: 全局作用域:脚本模式运行所有代码的默认作用域 函数作用域:由函数创建的作用域 局部作用域…

IP-Guard上传软件到软件中心服务器时,软件ID、显示名称等信息如何获取?

如何实现客户端只能从软件中心下载安装软件? 控制台设置禁止全部软件安装的软件管理策略即可。即使设置禁止,软件中心安装不会受影响的。 在控制台-策略-软件安装管理策略,勾选禁止全部软件的安装。 软件中心客户端下载安装软件后,下载安装包是否会自动删除? 1、http方式…

SpringBoot Actuator详解(四十八)

还是要开心的,万一梦想真得实现了呢 上一章简单介绍了SpringBoot日志配置(四十七) , 如果没有看过,请观看上一章 本章节详细参考了: https://www.cnblogs.com/caoweixiong/p/15325382.html Spring Boot Actuator 模块提供了生产级别的功能,比如健康检查…

.locked加密勒索数据库级别恢复---惜分飞

有客户数据库被加密成.locked结尾的扩展名,数据库无法正常使用 对应的READ_ME1.html文件中信息类似:send 0.1btc to my address:bc1ql8an5slxutu3yjyu9rvhsfcpv29tsfhv3j9lr4. contact email:servicehellowinter.online,if you can’t contact my email, please contact some d…

Leetcode-6425. 找到最长的半重复子字符串

题目描述 给你一个下标从 0 开始的字符串 s ,这个字符串只包含 0 到 9 的数字字符。 如果一个字符串 t 中至多有一对相邻字符是相等的,那么称这个字符串是 半重复的 。 请你返回 s 中最长 半重复 子字符串的长度。 一个 子字符串 是一个字符串中一段…

Redis第十章 Redis HyperLogLog与事务、Redis 7.0前瞻

HyperLogLog HyperLogLog(Hyper[ˈhaɪpə])并不是一种新的数据结构(实际类型为字符串类型),而是一种基数算法,通过 HyperLogLog 可以利用极小的内存空间完成独立总数的统计,数据集可以是 IP、Email、ID 等。 如果你的页面访问量非常大,比如…

JUC基础认识(2)

线程池(重点):3大方法,7大参数,4种拒绝策略 程序运行的本质:占用系统资源!优化资源的使用!----->池化技术 池化技术的好处: 1.降低资源的消耗 2.提高响应速度 3.方便管理 线程复用,可以控制最大…

Java ~ Reference ~ FinalizerHistogram【总结】

前言 文章 相关系列:《Java ~ Reference【目录】》(持续更新)相关系列:《Java ~ Reference ~ FinalizerHistogram【源码】》(学习过程/多有漏误/仅作参考/不再更新)相关系列:《Java ~ Referenc…

通俗讲解元学习(Meta-Learning)

元学习通俗的来说,就是去学习如何学习(Learning to learn),掌握学习的方法,有时候掌握学习的方法比刻苦学习更重要! 下面我们进行详细讲解 1. 从传统机器学习到元学习 传统的机器学中,我们选择一个算法&…

Fiddler汉化(一箭三连)

我安装的fiddler 操作系统是:Win10 64Bit 操作系统的版本号是:v5.0.20194.41348 for .NET 4.6.1 fiddler下载地址: 我用夸克网盘分享了「02-Web调试工具-FiddlerSetup.exe」,点击链接即可保存。 链接:https://pan.quar…

python调用go语言的代码

最近在学习上述Python,go语言,研究下互相调用的事项,在windows下使用 linux下: gcc -o libpycall.so -shared -fPIC pycall.c windows下: gcc -o libpycall.dll -shared -fPIC pycall.c 但是实际的过程中编译成*.so…

使用扩展卡尔曼滤波(EKF)融合激光雷达和雷达数据(Matlab代码实现)

💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…

复习js的Object.defineProperty()

今天在看尚硅谷的Vue2时,讲到了Object.defineProperty()方法,有点忘了,所以找资料复习了一下。 总说 Object.defineProperty()是一种用于定义对象属性的JavaScript方法,它允许开发人员精确控制一个属性的行为,并可以…

【MySQL】库表的基本操作

文章目录 数据库的基本操作增查删改切入查看当前使用数据库 数据表的基本操作增查所有表查指定表查表字段信息删表改表名改字段名改字段属性增字段删字段 数据表的约束主键约束(PRIMARY KEY)非空约束(NOT NULL)默认值约束(DEFAULT)唯一性约束(UNIQUE)外键约束(FOREIGN KEY)删除…

VSCode 配置 C++ 环境

文章目录 VSCode 配置 C 环境1. 配置 C 编译器1.1 下载 MinGW1.2 添加环境变量1.3 测试 2. 配置编辑器 VSCode2.1 下载 VSCode2.2 安装插件2.3 运行代码 3. 优化Reference VSCode 配置 C 环境 VSCode(Visual Studio Code)是一款轻量级的代码编辑器&…

Linux系统之部署Node.js环境

Linux系统之部署Node.js环境 一、Node.js介绍1.1 Node.js简介1.2 npm简介1.3 Node.js官网 二、本地环境介绍2.1 本地环境规划2.2 本次实践介绍 三、部署Node.js环境3.1 下载Node.js安装包3.2 解压Node.js安装包3.3 复制二进制文件3.4 配置环境变量3.5 检查node及npm版本 四、NP…

EXCEL的自定义格式,比如 @_ ,一个我很少用的功能的简单总结

目录 前言 1 入口: 2 有用的一些功能 2.1 可以自动在 你输入的字符串前后加 预设内容 2.1.1 具体见下面 2.1.2 对比EXCEL得常用拖动功能 2.2 设置填充下划线 2.2.1 效果如下 2.2.2 问题 2.2.3 下划线的显示问题 2.3 比如 #.## 2.4 比如 000.000 2.5 比…

自动化测试webdriver常用API总结

目录 前言: 1. selenium的简单介绍 2. selnium的下载 3. WebDriver常用API 前言: WebDriver 是一个自动化测试工具,主要用于模拟浏览器行为,实现自动化测试。下面是一些 WebDriver 常用 API 总结: 1. selenium的…

STM32--ESP8266物联网WIFI模块(贝壳物联)--远程无线控制点灯

本文适用于STM32F103C8T6等MCU,其他MCU可以移植,完整资源见文末链接 一、简介 随着移动物联网的发展,各场景下对于物联控制、数据上传、远程控制的诉求也越来越多,基于此乐鑫科技推出了便宜好用性价比极高的wifi物联模块——ESP…