首页 > 编程语言 >后端程序员必备:索引失效的十大杂症

后端程序员必备:索引失效的十大杂症

时间:2022-10-14 10:09:30浏览次数:57  
标签:杂症 索引 必备 userId 程序员 user KEY NULL id

背景

最近生产爆出一条慢sql,原因是用了or和!=,导致索引失效。于是,总结了索引失效的十大杂症,希望对大家有帮助,加油。

一、查询条件包含or,可能导致索引失效

新建一个user表,它有一个普通索引userId,结构如下:

  1. ​CREATE TABLE `user` (​
  2. ​ `id` int(11) NOT NULL AUTO_INCREMENT,​
  3. ​ `userId` int(11) NOT NULL,​
  4. ​ `age` int(11) NOT NULL,​
  5. ​ `name` varchar(255) NOT NULL,​
  6. ​ PRIMARY KEY (`id`),​
  7. ​ KEY `idx_userId` (`userId`)​
  8. ​) ENGINE=InnoDB DEFAULT CHARSET=utf8;​
  9. 执行一条查询sql,它是会走索引的,如下图所示: 后端程序员必备:索引失效的十大杂症_表结构
  10. 把or条件+没有索引的age加上,并不会走索引,如图: 后端程序员必备:索引失效的十大杂症_字段_02

分析&结论:

  • 对于or+没有索引的age这种情况,假设它走了userId的索引,但是走到age查询条件时,它还得全表扫描,也就是需要三步过程:全表扫描+索引扫描+合并
  • 如果它一开始就走全表扫描,直接一遍扫描就完事。
  • mysql是有优化器的,处于效率与成本考虑,遇到or条件,让索引失效,看起来也合情合理嘛。

注意: 如果or条件的列都加了索引,索引可能会走的,大家可以自己试一试。

二、如何字段类型是字符串,where时一定用引号括起来,否则索引失效

假设demo表结构如下:

  1. ​CREATE TABLE `user` (​
  2. ​ `id` int(11) NOT NULL AUTO_INCREMENT,​
  3. ​ `userId` varchar(32) NOT NULL,​
  4. ​ `name` varchar(255) NOT NULL,​
  5. ​ PRIMARY KEY (`id`),​
  6. ​ KEY `idx_userId` (`userId`) USING BTREE​
  7. ​) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;​

userId为字符串类型,是B+树的普通索引,如果查询条件传了一个数字过去,它是不走索引的,如图所示:后端程序员必备:索引失效的十大杂症_字段_03

如果给数字加上'',也就是传一个字符串呢,当然是走索引,如下图:

后端程序员必备:索引失效的十大杂症_联合索引_04

分析与结论:

为什么第一条语句未加单引号就不走索引了呢?这是因为不加单引号时,是字符串跟数字的比较,它们类型不匹配,MySQL会做隐式的类型转换,把它们转换为浮点数再做比较。

三、like通配符可能导致索引失效。

并不是用了like通配符,索引一定失效,而是like查询是以%开头,才会导致索引失效。

表结构:

  1. ​CREATE TABLE `user` (​
  2. ​ `id` int(11) NOT NULL AUTO_INCREMENT,​
  3. ​ `userId` varchar(32) NOT NULL,​
  4. ​ `name` varchar(255) NOT NULL,​
  5. ​ PRIMARY KEY (`id`),​
  6. ​ KEY `idx_userId` (`userId`) USING BTREE​
  7. ​) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;​

like查询以%开头,索引失效,如图:后端程序员必备:索引失效的十大杂症_表结构_05

把%放后面,发现索引还是正常走的,如下:后端程序员必备:索引失效的十大杂症_字段_06

把%加回来,改为只查索引的字段(覆盖索引),发现还是走索引,惊不惊喜,意不意外后端程序员必备:索引失效的十大杂症_表结构_07

结论:

like查询以%开头,会导致索引失效。可以有两种方式优化:

  • 使用覆盖索引
  • 把%放后面

附: 索引包含所有满足查询需要的数据的索引,称为覆盖索引(Covering Index)。

四、联合索引,查询时的条件列不是联合索引中的第一个列,索引失效。

表结构:(有一个联合索引 ​​idx_userid_age​​​, ​​userId​​​在前, ​​age​​在后)

  1. ​CREATE TABLE `user` (​
  2. ​ `id` int(11) NOT NULL AUTO_INCREMENT,​
  3. ​ `userId` int(11) NOT NULL,​
  4. ​ `age` int(11) DEFAULT NULL,​
  5. ​ `name` varchar(255) NOT NULL,​
  6. ​ PRIMARY KEY (`id`),​
  7. ​ KEY `idx_userid_age` (`userId`,`age`) USING BTREE​
  8. ​) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;​

在联合索引中,查询条件满足最左匹配原则时,索引是正常生效的。请看demo:

后端程序员必备:索引失效的十大杂症_表结构_08

后端程序员必备:索引失效的十大杂症_表结构_09

如果条件列不是联合索引中的第一个列,索引失效,如下:

后端程序员必备:索引失效的十大杂症_字段_10

分析与结论:

  • 当我们创建一个联合索引的时候,如(k1,k2,k3),相当于创建了(k1)、(k1,k2)和(k1,k2,k3)三个索引,这就是最左匹配原则
  • 联合索引不满足最左原则,索引一般会失效,但是这个还跟Mysql优化器有关的。

五、在索引列上使用mysql的内置函数,索引失效。

表结构:

  1. ​CREATE TABLE `user` (​
  2. ​ `id` int(11) NOT NULL AUTO_INCREMENT,​
  3. ​ `userId` varchar(32) NOT NULL,​
  4. ​ `loginTime` datetime NOT NULL,​
  5. ​ PRIMARY KEY (`id`),​
  6. ​ KEY `idx_userId` (`userId`) USING BTREE,​
  7. ​ KEY `idx_login_time` (`loginTime`) USING BTREE​
  8. ​) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;​

虽然loginTime加了索引,但是因为使用了mysql的内置函数Date_ADD(),索引直接GG,如图:后端程序员必备:索引失效的十大杂症_字段_11

六、对索引列运算(如,+、-、*、/),索引失效。

表结构:

  1. ​CREATE TABLE `user` (​
  2. ​ `id` int(11) NOT NULL AUTO_INCREMENT,​
  3. ​ `userId` varchar(32) NOT NULL,​
  4. ​ `age` int(11) DEFAULT NULL,​
  5. ​ PRIMARY KEY (`id`),​
  6. ​ KEY `idx_age` (`age`) USING BTREE​
  7. ​) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;​

虽然age加了索引,但是因为它进行运算,索引直接迷路了。。。山重水复疑无路,算着算着脑瓜疼,索引就真的不认识路了。如图:

后端程序员必备:索引失效的十大杂症_联合索引_12

七、索引字段上使用(!= 或者 < >,not in)时,可能会导致索引失效。

表结构:

  1. ​CREATE TABLE `user` (​
  2. ​ `id` int(11) NOT NULL AUTO_INCREMENT,​
  3. ​ `userId` int(11) NOT NULL,​
  4. ​ `age` int(11) DEFAULT NULL,​
  5. ​ `name` varchar(255) NOT NULL,​
  6. ​ PRIMARY KEY (`id`),​
  7. ​ KEY `idx_age` (`age`) USING BTREE​
  8. ​) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;​

虽然age加了索引,但是使用了!= 或者 < >,not in这些时,索引如同虚设。如下:

后端程序员必备:索引失效的十大杂症_表结构_13

后端程序员必备:索引失效的十大杂症_字段_14

八、索引字段上使用is null, is not null,可能导致索引失效。

表结构:

  1. ​CREATE TABLE `user` (​
  2. ​ `id` int(11) NOT NULL AUTO_INCREMENT,​
  3. ​ `card` varchar(255) DEFAULT NULL,​
  4. ​ `name` varchar(255) DEFAULT NULL,​
  5. ​ PRIMARY KEY (`id`),​
  6. ​ KEY `idx_name` (`name`) USING BTREE,​
  7. ​ KEY `idx_card` (`card`) USING BTREE​
  8. ​) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;​

单个name字段加上索引,并查询name为非空的语句,其实会走索引的,如下:

后端程序员必备:索引失效的十大杂症_联合索引_15

单个card字段加上索引,并查询name为非空的语句,其实也会走索引的,如下:后端程序员必备:索引失效的十大杂症_联合索引_16

但是它们用or连接起来,索引就失效了,如下:

后端程序员必备:索引失效的十大杂症_联合索引_17

九、左连接查询或者右连接查询查询关联的字段编码格式不一样,可能导致索引失效。

新建两个表,一个user,一个user_job

  1. ​CREATE TABLE `user` (​
  2. ​ `id` int(11) NOT NULL AUTO_INCREMENT,​
  3. ​ `name` varchar(255) CHARACTER SET utf8mb4 DEFAULT NULL,​
  4. ​ `age` int(11) NOT NULL,​
  5. ​ PRIMARY KEY (`id`),​
  6. ​ KEY `idx_name` (`name`) USING BTREE​
  7. ​) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;​

  8. ​CREATE TABLE `user_job` (​
  9. ​ `id` int(11) NOT NULL,​
  10. ​ `userId` int(11) NOT NULL,​
  11. ​ `job` varchar(255) DEFAULT NULL,​
  12. ​ `name` varchar(255) DEFAULT NULL,​
  13. ​ PRIMARY KEY (`id`),​
  14. ​ KEY `idx_name` (`name`) USING BTREE​
  15. ​) ENGINE=InnoDB DEFAULT CHARSET=utf8;​

user 表的name字段编码是utf8mb4,而user_job表的name字段编码为utf8。

后端程序员必备:索引失效的十大杂症_联合索引_18

后端程序员必备:索引失效的十大杂症_表结构_19

执行左外连接查询,user_job表还是走全表扫描,如下:

后端程序员必备:索引失效的十大杂症_字段_20

如果把它们改为name字段编码一致,还是会一路高歌,雄赳赳,气昂昂,走向索引。

后端程序员必备:索引失效的十大杂症_字段_21

十、mysql估计使用全表扫描要比使用索引快,则不使用索引。

  • 当表的索引被查询,会使用最好的索引,除非优化器使用全表扫描更有效。优化器优化成全表扫描取决与使用最好索引查出来的数据是否超过表的30%的数据。
  • 不要给'性别'等增加索引。如果某个数据列里包含了均是"0/1"或“Y/N”等值,即包含着许多重复的值,就算为它建立了索引,索引效果不会太好,还可能导致全表扫描。


Mysql出于效率与成本考虑,估算全表扫描与使用索引,哪个执行快。这跟它的优化器有关,来看一下它的逻辑架构图吧(图片来源网上)

后端程序员必备:索引失效的十大杂症_字段_22

总结

总结了索引失效的十大杂症,在这里来个首尾呼应吧,分析一下我们生产的那条慢sql。模拟的表结构与肇事sql如下:

  1. ​CREATE TABLE `user_session` (​
  2. ​ `user_id` varchar(32) CHARACTER SET utf8mb4 NOT NULL,​
  3. ​ `device_id` varchar(64) NOT NULL,​
  4. ​ `status` varchar(2) NOT NULL,​
  5. ​ `create_time` datetime NOT NULL,​
  6. ​ `update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,​
  7. ​ PRIMARY KEY (`user_id`,`device_id`) USING BTREE​
  8. ​) ENGINE=InnoDB DEFAULT CHARSET=utf8;​
  9. ​explain​
  10. ​update user_session set status =1​
  11. ​where (`user_id` = '1' and `device_id`!='2')​
  12. ​or (`user_id` != '1' and `device_id`='2')​

分析:

  • 执行的sql,使用了 ​​or​​​条件,因为组合主键( ​​user_id​​, ​​device_id​​),看起来像是每一列都加了索引,索引会生效。
  • 但是出现 ​​!=​​​,可能导致索引失效。也就是 ​​or​​+ ​​!=​​两大综合症,导致了慢更新sql。

解决方案:

那么,怎么解决呢?我们是把 ​​or​​​条件拆掉,分成两条执行。同时给 ​​device_id​​加一个普通索引。

最后,总结了索引失效的十大杂症,希望大家在工作学习中,参考这十大杂症多点结合执行计划 ​​expain​​和场景,具体分析,而不是按部就班,墨守成规,认定哪个情景一定索引失效等等。

个人公众号

后端程序员必备:索引失效的十大杂症_表结构_23


    标签:杂症,索引,必备,userId,程序员,user,KEY,NULL,id
    From: https://blog.51cto.com/u_15659694/5755198

    相关文章

    • Java程序员必备:异常的十个关键知识点
      前言总结了Java异常十个关键知识点,面试或者工作中都有用哦,加油。一.异常是什么异常是指阻止当前方法或作用域继续执行的问题。比如你读取的文件不存在,数组越界,进行除法时,除......
    • Java程序员必备:查看日志常用的linux命令
      前言趁周末,复习一下鸟哥的linux私房菜,看了文件内容查阅部分,做个笔记,哈哈,希望对你有帮助哦。catcat:由第一行开始显示文件所有内容参数说明​​cat[-AbEnTv]​​​​参数:......
    • leetcode必备算法:聊聊滑动窗口
      前言我们刷leetcode的时候,经常会遇到滑动窗口类型题目。滑动窗口问题非常经典,也很有技巧性,一般大厂也喜欢问。今天跟大家一起来学习滑动窗口的套路,文章如果有不正确的地方,......
    • 面试必备:虾皮服务端15连问
      前言大家好,我是程序员田螺,最近有位读者去虾皮面试啦,分享一下面试的真题~排序链表对称与非对称加密算法的区别TCP如何保证可靠性聊聊五种IO模型hystrix工作原理延时场景处......
    • 一波程序员搞笑动态图
      1、当我改完最后一个Bug,以为可以关闭bug单的时候…2.当你的代码没有错误,没有警告,一次便编译通过时~3.当我开始使用新框架,却忘记阅读文档时4.放完假,第一天回来上班的你5、......
    • 前端程序员各个阶段规划以及提升
      校招阶段:选择方向:选择方向:建议在大三第一个学期的时候就需要选择好方向,是选择就业还是考研,就业选择技术类方向,还是开发类,需要明确方向,有充足的准备,找工作的时候才不......
    • 【CSS】必备的22个CSS小技巧
      大家好,今天我们将会介绍一些非常实用的CSS小技巧,让我们开始吧!混合模式之前Firefox和Safari浏览器已经开始支持类似Photoshop的混合模式,但是在Chrome和Opera浏览器中需要添加......
    • 文献综述必备工具Connected Papers如何使用
      ConnectedPapers是一款知名的文献分析工具。只要输入要分析论文的以下信息(doi、arXivurl、标题、SemanticScholarurl或PubMedurl),就可查询到该论文的引用网络、先前研......
    • 程序员应该具备的优秀习惯
      积极主动态度乐观,敢于尝试行动主动承担推进 独立思考理解需求沟通交流理解原理探索未知创新性思维持续学习养成学习的习惯......
    • 作为程序员,你还在用B站学习?别做梦了
      前一段时间,美国顶级律师和人工智能进行了一场比赛。 在这比赛中,人类律师的平均准确率在85%左右,而AI准确率高达95%。人类律师完成任务平均时间在92分钟,AI人工智能仅仅只用了......