mysql 索引失效总结
- 特别说明
- MySQL索引失效的常见场景:
- 单个索引
- where语句中索引列使用了负向查询,可能会导致索引失效
- 在索引列上使用内置函数,一定会导致索引失效
- OR引起的索引失效
- 索引字段可以为null,使用is null或is not null时,可能会导致索引失效
- 运算符导致的索引失效
- 模糊搜索导致的索引失效
- 组合索引
- 其他情况
- 总结:
基于上次分享时在项目统计数据中出现的索引失效问题, 总结下哪些情况会导致索引失效,在以后工作中尽量能防止索引失效的情况。
特别说明
explain是一个好习惯!
MySQL索引失效的常见场景:
在验证下面的场景时,请准备足够多的数据量,因为数据量少时,MySQL的优化器有时会判定全表扫描无伤大雅,就不会命中索引了。 测试使用的一张表结构以及一些测试数据:
CREATE TABLE `user` (
`id` int(5) unsigned NOT NULL AUTO_INCREMENT,
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`name` varchar(5) NOT NULL,
`age` tinyint(2) unsigned zerofill NOT NULL,
`sex` char(1) NOT NULL,
`mobile` char(12) NOT NULL DEFAULT '',
`address` char(120) DEFAULT NULL,
`height` varchar(10) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_createtime` (`create_time`) USING BTREE,
KEY `idx_name_age_sex` (`name`,`sex`,`age`) USING BTREE,
KEY `idx_ height` (`height`) USING BTREE,
KEY `idx_address` (`address`) USING BTREE,
KEY `idx_age` (`age`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=261 DEFAULT CHARSET=utf8;
INSERT INTO `pdb`.`user` (`id`, `name`, `age`, `sex`, `mobile`, `address`, `height`) VALUES ('1', '冰峰', '22', '男', '1', '陕西省咸阳市彬县', '175');
INSERT INTO `pdb`.`user` (`id`, `name`, `age`, `sex`, `mobile`, `address`, `height`) VALUES ('2', '松子', '13', '女', '1', NULL, '180');
INSERT INTO `pdb`.`user` (`id`, `name`, `age`, `sex`, `mobile`, `address`, `height`) VALUES ('3', '蚕豆', '20', '女', '1', NULL, '180');
INSERT INTO `pdb`.`user` (`id`, `name`, `age`, `sex`, `mobile`, `address`, `height`) VALUES ('4', '冰峰', '20', '男', '17765010977', '陕西省西安市', '155');
INSERT INTO `pdb`.`user` (`id`, `name`, `age`, `sex`, `mobile`, `address`, `height`) VALUES ('255', '竹笋', '22', '男', '我测试下可以储存几个中文', NULL, '180');
INSERT INTO `pdb`.`user` (`id`, `name`, `age`, `sex`, `mobile`, `address`, `height`) VALUES ('256', '冰峰', '21', '女', '', NULL, '167');
INSERT INTO `pdb`.`user` (`id`, `name`, `age`, `sex`, `mobile`, `address`, `height`) VALUES ('257', '小红', '20', '男', '', NULL, '180');
INSERT INTO `pdb`.`user` (`id`, `name`, `age`, `sex`, `mobile`, `address`, `height`) VALUES ('258', '小鹏', '20', '男', '', NULL, '188');
INSERT INTO `pdb`.`user` (`id`, `name`, `age`, `sex`, `mobile`, `address`, `height`) VALUES ('259', '张三', '20', '男', '', NULL, '180');
INSERT INTO `pdb`.`user` (`id`, `name`, `age`, `sex`, `mobile`, `address`, `height`) VALUES ('260', '李四', '22', '男', '', NULL, '165');
单个索引
where语句中索引列使用了负向查询,可能会导致索引失效
负向查询包括:NOT、!=、<>、!<、!>、NOT IN、NOT LIKE、NOT EXISTS等 其实负向查询并不绝对会索引失效,这要看MySQL优化器的判断,全表扫描或者走索引哪个成本低。 所以如果数据量大的话,谨慎使用负向查询
SELECT * FROM `user` WHERE `name` != '冰峰';
在索引列上使用内置函数,一定会导致索引失效
比如下面语句中索引列login_time上使用了函数,会索引失效:
select * from `user` where DATE_ADD(login_time, INTERVAL 1 DAY) = 7;
以下几种索引失效场景,应该都归于索引列使用了函数
隐式类型转换导致的索引失效
比如下面语句中索引列user_id为varchar类型,不会命中索引
select * from `user` where user_id = 12;
这是因为MySQL做了隐式类型转换,调用函数将user_id做了转换。
select * from `user` where CAST(user_id AS signed int) = 12;
类型不一致导致的索引失效
在说这个之前,一定要说一下设计表字段的时候,千万、一定、必须要保持字段类型的一致性!这个同2.1的原因 比如user表的id是int自增,到了用户的账户表user_id这个字段,一定、必须也是int类型,千万不要写成varchar、char什么的骚操作。 设计字段的时候一定要保持类型的一致性,如果你不保证一致性,一个int一个varchar,在进行多表联合查询(eg: 1 = '1')隐式类型转换,必然走不了索引
隐式字符编码转换导致的索引失效
当两个表之间做关联查询时,如果两个表中关联的字段字符编码不一致的话,MySQL可能会调用CONVERT函数,将不同的字符编码进行隐式转换从而达到统一。作用到关联的字段时,就会导致索引失效。
比如下面这个语句,其中d.tradeid字符编码为utf8,而l.tradeid的字符编码为utf8mb4。因为utf8mb4是utf8的超集,所以MySQL在做转换时会用CONVERT将utf8转为utf8mb4。简单来看就是CONVERT作用到了d.tradeid上,因此索引失效。
select l.operator from tradelog l , trade_detail d where d.tradeid=l.tradeid and d.id=4;
这种情况一般有两种解决方案。 方案1: 将关联字段的字符编码统一。 方案2: 实在无法统一字符编码时,手动将CONVERT函数作用到关联时=的右侧,起到字符编码统一的目的,这里是强制将utf8mb4转为utf8,当然从超集向子集转换是有数据截断风险的。如下:
select d.* from tradelog l , trade_detail d where d.tradeid=CONVERT(l.tradeid USING utf8) and l.id=2;
OR引起的索引失效
使用or并不是一定会使索引失效,需要看or左右两边的查询列是否命中相同的索引。
下面这条语句其实是命中索引的(据说和MySQL版本有关系,某个版本后才会有效,可以使用explain验证下)
select * from `user` where user_id = 1 or user_id = 2;
但是这条语句是无法命中索引的:
select * from `user` where user_id = 1 or age = 20;
假设age列也有索引的话,依然是无法命中索引的。
有建议说,尽量避免使用or语句,可以根据情况尽量使用union all或者in来代替,这两个语句的执行效率也比or好些。
索引字段可以为null,使用is null或is not null时,可能会导致索引失效
其实单个索引字段,使用is null或is not null时,是可以命中索引的。
explain
SELECT * FROM `user` WHERE address IS NULL;
explain
SELECT * FROM `user` WHERE address IS NOT NULL;
大部分使用场景是,两个不同索引字段用or连接时,索引就失效了,确实索引失效,但这个锅应该由or来背。
但是,建议设计字段的时候,要设为not null并提供默认值(如果没有必要的要求必须为NULL,那么最好给个默认值空字符串) null的列使索引/索引统计/值比较都更加复杂,对MySQL来说更难优化。 null 这种类型MySQL内部需要进行特殊处理,增加数据库处理记录的复杂性;同等条件下,表中有较多空字段的时候,数据库的处理性能会降低很多。 null值需要更多的存储空,无论是表还是索引中每行中的null的列都需要额外的空间来标识。 对null 的处理时候,只能采用is null或is not null,而不能采用=、in、<、<>、!=、not in这些操作符号。如:where name!='shenjian',如果存在name为null值的记录,查询结果就不会包含name为null值的记录。
运算符导致的索引失效
运算如+,-,*,/等,如下:
select * from `user` where age - 1 = 10;
优化的话,要把运算放在值上,或者在应用程序中直接算好,比如:
select * from `user` where age = 10 - 1;
模糊搜索导致的索引失效
SELECT * FROM `user` WHERE `name` LIKE '%李';
like查询以%开头时,会导致索引失效。解决办法有两种: 将%移到后面,如:
select * from `user` where `name` like '李%';
利用覆盖索引来命中索引。
select name from `user` where `name` like '%李%';
组合索引
违背最左匹配原则,一定会导致索引失效
当创建一个联合索引的时候,如(k1,k2,k3),相当于创建了(k1)、(k1,k2)和(k1,k2,k3)三个索引,它的索引的顺序是从左往右依次进行比较的,这就是最左匹配原则。
下面的语句就不会命中索引:
select * from t where k2=2;
select * from t where k3=3;
slect * from t where k2=2 and k3=3;
下面的语句只会命中索引(k1):
slect * from t where k1=1 and k3=3;
具体情况在user表中, 有组合索引:(name,sex,age)
EXPLAIN SELECT * FROM `user` WHERE sex = '男';
这种情况,name走索引。
EXPLAIN SELECT * FROM `user` WHERE name = '冰峰' AND sex = '男';
这种情况,name走索引,接下来会去找age,结果条件中没有age, 那么后面的sex也将不走索引。
SELECT * FROM `user` WHERE sex = '男' AND age = 22 AND `name` = '冰峰';
跟sql脚本语句中的书写顺序是没有任何关系的,这个组合索引是有效的,因为mysql优化器的底层会帮我们做一个优化,它会把你的SQL优化为它认为一个效率最高的样子进行执行。
如果使用了!=会导致后面的索引全部失效
SELECT * FROM `user` WHERE sex = '男' AND `name` != '冰峰' AND age = 22;
在name字段使用了 != ,由于name字段是最左边的一个字段,根据最左匹配原则,如果name不走索引,后面的字段也将不走索引。
其他情况
MySQL优化器的最终选择,可能会不走索引(这个可能也和版本有关系)
上面有提到,即使完全符合索引生效的场景,考虑到实际数据量等原因,最终是否使用索引还要看MySQL优化器的判断。当然你也可以在sql语句中写明强制走某个索引。
总结:
并不是所以的表都需要去建立索引。 对于一些业务数据,可能量比较大了,查询数据已经有了一点压力,那么最简单、快速的办法就是建立合适的索引。 但是有些业务可能表里就没多少数据,或者表的使用频率非常不高的情况下是没必要必须要去做索引的。
禁止在更新十分频繁、区分度不高的属性上建立索引 更新会变更B+树,更新频繁的字段建立索引会大大降低数据库性能 “性别”这种区分度不大的属性,建立索引是没有什么意义的,不能有效过滤数据,性能与全表扫描类似 组合索引,必须把区分度高的字段放在前面
转载于: https://www.jb51.net/article/195223.htm https://www.jb51.net/article/176633.htm