最近生产爆出一条慢sql,缘由是用了or和!=,致使索引失效。因而,总结了索引失效的十大杂症,但愿对你们有帮助,加油。mysql
新建一个user表,它有一个普通索引userId,结构以下:sql
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;
复制代码
分析&结论:bash
注意: 若是or条件的列都加了索引,索引可能会走的,你们能够本身试一试。session
假设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;
复制代码
userId为字符串类型,是B+树的普通索引,若是查询条件传了一个数字过去,它是不走索引的,如图所示: 函数
若是给数字加上'',也就是传一个字符串呢,固然是走索引,以下图:学习
分析与结论:优化
为何第一条语句未加单引号就不走索引了呢? 这是由于不加单引号时,是字符串跟数字的比较,它们类型不匹配,MySQL会作隐式的类型转换,把它们转换为浮点数再作比较。ui
并非用了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查询以%开头,索引失效,如图:
把%放后面,发现索引仍是正常走的,以下:
把%加回来,改成只查索引的字段(覆盖索引),发现仍是走索引,惊不惊喜,意不意外
结论:
like查询以%开头,会致使索引失效。能够有两种方式优化:
附: 索引包含全部知足查询须要的数据的索引,称为覆盖索引(Covering Index)。
表结构:(有一个联合索引idx_userid_age
,userId
在前,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;
复制代码
在联合索引中,查询条件知足最左匹配原则时,索引是正常生效的。请看demo:
若是条件列不是联合索引中的第一个列,索引失效,以下:
分析与结论:
表结构:
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;
复制代码
虽然loginTime加了索引,可是由于使用了mysql的内置函数Date_ADD(),索引直接GG,如图:
表结构:
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;
复制代码
虽然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_age` (`age`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
复制代码
虽然age加了索引,可是使用了!= 或者 < >,not in这些时,索引如同虚设。以下:
表结构:
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;
复制代码
单个name字段加上索引,并查询name为非空的语句,其实会走索引的,以下:
单个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 表的name字段编码是utf8mb4,而user_job表的name字段编码为utf8。
执行左外链接查询,user_job表仍是走全表扫描,以下:
若是把它们改成name字段编码一致,仍是会走索引。
当表的索引被查询,会使用最好的索引,除非优化器使用全表扫描更有效。优化器优化成全表扫描取决与使用最好索引查出来的数据是否超过表的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;
复制代码
explain
update user_session set status =1
where (`user_id` = '1' and `device_id`!='2')
or (`user_id` != '1' and `device_id`='2')
复制代码
分析:
or
条件,由于组合主键(user_id
,device_id
),看起来像是每一列都加了索引,索引会生效。!=
,可能致使索引失效。也就是or
+!=
两大综合症,致使了慢更新sql。解决方案:
那么,怎么解决呢?咱们是把or
条件拆掉,分红两条执行。同时给device_id
加一个普通索引。
最后,总结了索引失效的十大杂症,但愿你们在工做学习中,参考这十大杂症,多点结合执行计划expain
和场景,具体分析 ,而不是循序渐进,墨守成规,认定哪一个情景必定索引失效。