以前有看过许多相似的文章内容,提到过一些sql语句的使用不当会致使MySQL的索引失效。还有一些MySQL“军规”或者规范写明了某些sql不能这么写,不然索引失效。程序员
绝大部分的内容笔者是承认的,不过部分举例中笔者认为用词太绝对了,并无说明其中的起因,不少人不知道为何。因此笔者绝对再整理一遍MySQL中索引失效的常见场景,并分析其中的起因供你们参考。sql
固然请记住,explain是一个好习惯!数据库
在验证下面的场景时,请准备足够多的数据量,由于数据量少时,MySQL的优化器有时会断定全表扫描无伤大雅,就不会命中索引了。后端
使用or并非必定会使索引失效,你须要看or左右两边的查询列是否命中相同的索引。bash
假设USER表中的user_id列有索引,age列没有索引。markdown
下面这条语句实际上是命中索引的(听说是新版本的MySQL才能够,若是你使用的是老版本的MySQL,可使用explain验证下)。架构
select * from `user` where user_id = 1 or user_id = 2; 复制代码
可是这条语句是没法命中索引的。函数
select * from `user` where user_id = 1 or age = 20; 复制代码
假设age列也有索引的话,依然是没法命中索引的。oop
select * from `user` where user_id = 1 or age = 20; 复制代码
所以才有建议说,尽可能避免使用or语句,能够根据状况尽可能使用union all或者in来代替,这两个语句的执行效率也比or好些。性能
负向查询包括:NOT、!=、<>、!<、!>、NOT IN、NOT LIKE等。
某“军规”中说,使用负向查询必定会索引失效,笔者查了些文章,有网友对这点进行了反驳并举证。
其实负向查询并不绝对会索引失效,这要看MySQL优化器的判断,全表扫描或者走索引哪一个成本低了。
其实单个索引字段,使用is null或is not null时,是能够命中索引的,但网友在举证时说两个不一样索引字段用or链接时,索引就失效了,笔者认为确实索引失效,但这个锅应该由or来背,属于第一种场景~~
假设USER表中的user_id列有索引且容许null,age列有索引且容许null。
select * from `user` where user_id is not null or age is not null; 复制代码
不过某些“军规”和规范中都有强调,字段要设为not null并提供默认值,是有缘由值得参考的。
- null的列使索引/索引统计/值比较都更加复杂,对MySQL来讲更难优化。
- null 这种类型MySQL内部须要进行特殊处理,增长数据库处理记录的复杂性;同等条件下,表中有较多空字段的时候,数据库的处理性能会下降不少。
- null值须要更多的存储空,不管是表仍是索引中每行中的null的列都须要额外的空间来标识。
- 对null 的处理时候,只能采用is null或is not null,而不能采用=、in、<、<>、!=、not in这些操做符号。如:where name!=’shenjian’,若是存在name为null值的记录,查询结果就不会包含name为null值的记录。
好比下面语句中索引列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; 复制代码
当两个表之间作关联查询时,若是两个表中关联的字段字符编码不一致的话,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; 复制代码
运算如+,-,*,/等,以下:
select * from `user` where age - 1 = 10; 复制代码
优化的话,要把运算放在值上,或者在应用程序中直接算好,好比:
select * from `user` where age = 10 - 1; 复制代码
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; 复制代码
上面有提到,即便彻底符合索引生效的场景,考虑到实际数据量等缘由,最终是否使用索引还要看MySQL优化器的判断。固然你也能够在sql语句中写明强制走某个索引。