背景

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

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

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

CREATE TABLE `user` (  `id` int(11) NOT NULL AUTO_INCREMENT,  `userId` int(11) NOT NULL,  `age` int(11) NOT NULL,  `name` varchar(255) NOT NULL,  PRIMARY KEY (`id`),  KEY `idx_userId` (`userId`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;

把or条件+没有索引的age加上,并不会走索引,如图:

分析&结论:

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

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

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

假设demo表结构如下:

CREATE TABLE `user` (  `id` int(11) NOT NULL AUTO_INCREMENT,  `userId` varchar(32) NOT NULL,  `name` varchar(255) NOT NULL,  PRIMARY KEY (`id`),  KEY `idx_userId` (`userId`) USING BTREE) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

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

分析与结论:

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

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

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

表结构:

CREATE TABLE `user` (  `id` int(11) NOT NULL AUTO_INCREMENT,  `userId` varchar(32) NOT NULL,  `name` varchar(255) NOT NULL,  PRIMARY KEY (`id`),  KEY `idx_userId` (`userId`) USING BTREE) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

把%放后面,发现索引还是正常走的,如下:

把%加回来,改为只查索引的字段(覆盖索引),发现还是走索引,惊不惊喜,意不意外

结论:

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

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

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

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

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

CREATE TABLE `user` (  `id` int(11) NOT NULL AUTO_INCREMENT,  `userId` int(11) NOT NULL,  `age` int(11) DEFAULT NULL,  `name` varchar(255) NOT NULL,  PRIMARY KEY (`id`),  KEY `idx_userid_age` (`userId`,`age`) USING BTREE) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

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

分析与结论:

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

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

表结构:

CREATE TABLE `user` (  `id` int(11) NOT NULL AUTO_INCREMENT,  `userId` varchar(32) NOT NULL,  `loginTime` datetime NOT NULL,  PRIMARY KEY (`id`),  KEY `idx_userId` (`userId`) USING BTREE,  KEY `idx_login_time` (`loginTime`) USING BTREE) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

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

表结构:

CREATE TABLE `user` (  `id` int(11) NOT NULL AUTO_INCREMENT,  `userId` varchar(32) NOT NULL,  `age` int(11) DEFAULT NULL,  PRIMARY KEY (`id`),  KEY `idx_age` (`age`) USING BTREE) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

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

表结构:

CREATE TABLE `user` (  `id` int(11) NOT NULL AUTO_INCREMENT,  `userId` int(11) NOT NULL,  `age` int(11) DEFAULT NULL,  `name` varchar(255) NOT NULL,  PRIMARY KEY (`id`),  KEY `idx_age` (`age`) USING BTREE) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

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

表结构:

CREATE TABLE `user` (  `id` int(11) NOT NULL AUTO_INCREMENT,  `card` varchar(255) DEFAULT NULL,  `name` varchar(255) DEFAULT NULL,  PRIMARY KEY (`id`),  KEY `idx_name` (`name`) USING BTREE,  KEY `idx_card` (`card`) USING BTREE) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

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

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

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

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

CREATE TABLE `user` (  `id` int(11) NOT NULL AUTO_INCREMENT,  `name` varchar(255) CHARACTER SET utf8mb4 DEFAULT NULL,  `age` int(11) NOT NULL,  PRIMARY KEY (`id`),  KEY `idx_name` (`name`) USING BTREE) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; CREATE TABLE `user_job` (  `id` int(11) NOT NULL,  `userId` int(11) NOT NULL,  `job` varchar(255) DEFAULT NULL,  `name` varchar(255) DEFAULT NULL,  PRIMARY KEY (`id`),  KEY `idx_name` (`name`) USING BTREE) ENGINE=InnoDB DEFAULT CHARSET=utf8;

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

如果把它们改为name字段编码一致,还是会走索引。

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

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

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

总结

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

CREATE TABLE `user_session` (  `user_id` varchar(32) CHARACTER SET utf8mb4 NOT NULL,  `device_id` varchar(64) NOT NULL,  `status` varchar(2) NOT NULL,  `create_time` datetime NOT NULL,  `update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,  PRIMARY KEY (`user_id`,`device_id`) USING BTREE) ENGINE=InnoDB DEFAULT CHARSET=utf8;
  • 执行的sql,使用了or条件,因为组合主键(user_id,device_id),看起来像是每一列都加了索引,索引会生效。
  • 但是出现!=,可能导致索引失效。也就是or+!=两大综合症,导致了慢更新sql。

解决方案:

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

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

更多相关文章

  1. Android异步加载图像小结 (含线程池,缓存方法)
  2. android WebView 图片缩放功能小结
  3. Android(安卓)Camera 使用小结
  4. android fragment学习5--fragment扩展 TabLayout非自定义
  5. Android(安卓)电源系列小结s
  6. Android中自定义对话框小结
  7. Android中自定义对话框小结
  8. Android中自定义对话框小结
  9. Android(安卓)4.0设计规范 优先导读 十大改变

随机推荐

  1. Spring Security ACL使用MySQL配置与数据
  2. 多个mysql数据库怎么指定到多个不同文件
  3. mysql查询一条工单时间需要10秒。优化sql
  4. Centos7下编译Qt的mysql驱动
  5. MySQL基于Navicat的基本操作技巧
  6. MySql中有哪些存储引擎?
  7. 数据库行转列和列转行小例子
  8. mysql的zip版本安装填坑
  9. InnoDB辅助索引页面的物理结构是什么样子
  10. 一步一步学MySQL----9 条件数据记录查询