开发中很常见的一个问题就是,我明明设置了索引,但是为什么查询的时候失效了?
举个例子。
假设有表testtable有a,b,c,d四个字段,
索引abc:(a,b,c)有三个字段。
那么进行查询的时候根据条件的不同,索引的使用状况又是如何呢?
我们通过Explain关键词查看索引使用情况。
首先是最简单粗暴的SELECT * from testtable where a=1 and b=2 and c=3,也就是查询所有的索引字段。
结果是
我们看到它使用的key是abc,ref中是三个const,对应了我们where条件下的三个常量。
也就代表三个字段的索引都用上了。
但是如果我们没有三个字段全查呢?
我们来对比ab,ac和bc的情况。
按照正常逻辑,应该是分别用了ab、ac和bc的索引。
ab:
没毛病,和我们想象的一致,它通过了索引,并且使用了其中两个常量。
再看ac:
貌似和我们想得有些不同,虽然是通过了索引,但是只用了其中一个常量?
最后是bc:
可以看到,type从原本的ref直接变成了All,然后key也变成了Null?
也就是说,进行了全表扫描,并没有通过索引。
可是我们创建了abc的索引啊!同样是用其中的某些字段,为什么时而有用,时而没用呢?
我们回到之前所说的组合索引存储方式。
在建立组合索引时,遵从的也是B+Tree结构,依照上篇的分析,我们是先建立了a字段的B+Tree索引,在a字段的基础上,再建立b字段的B+Tree索引。
所以说,a是带头大哥,试想如果我们跳过了a,直接从b开始查询,会出现什么问题?
因为我们第一层结构是通过a来建立的,b的数据是根据a的数据情况,分散在a的索引的下方。
那如果要通过索引,跳过a单独查询b,会很困难,因为b的数据已经分散在各地了。
所以经过MySql的推算,在abc索引中根据bc条件查询的效率远远不如直接全表查询,因此最终选择放弃索引,进行全表查询。
同理,查询ac时因为没有了b,所以c的索引也失效,索引使用的部分只有a。
这就是mysql最左匹配原则,即查询从最左前列开始,一旦跳过了某列,那该列以及其之后的索引将不会生效。
满足了精确查找,那么还有新的问题,在实际业务中,很容易有范围查找的需求,那么范围查找对索引的影响是什么呢?
还是用刚才那张表举例,这次将条件继续修改。
SELECT * from testtable where a=1 and b<2 and c=3
可以看到type变成了range,而且用到了key也就是abc。
但是怎么看出来是不是用了abc三个索引呢?
我们看key_len字段,它代表了被使用的索引的长度。
此前我们查询abc的时候,用的是abc全部的索引,那时显示的key_len是15。
而这里是10,根据计算,我们可以知道每个字段索引的len是5,那10就代表只用了两个。
可是这次明明没有漏掉任何字段啊!
这里就需要通过索引的功能来解释了。
索引的两大功能是查找和排序,我们在精确查找的时候,用到的是查找功能,而在范围查找的时候,就要用到排序了。
再看B+Tree的那张图,发现叶子节点每段数据之间都有一个指针连接。
这个指针就是为范围查找而服务的。
使用范围查询就是先定位到条件的那个节点,然后通过大或小,将右或左的数据获取出来。
这样的话在范围查找后就完全绕开了索引,因此之后的索引也就都失效了。
所以得到的结论就是:索引范围条件右边的列不能被使用。
那除了这些,MySql还有很多其他关键词,他们对索引的影响又是怎样的呢?
一个常用的关键字是like。
我们更改上述sql
SELECT * from testtable where a=1 and b like ‘%2%’ and c=3
结果和此前有所不同,我们的type变成了ref,len变为了5,说明只用到了a的索引。
为什么呢?究其本质,like也相当于一个范围查询,而如果是双边的通配符,代表了查询所有含关键字的结果,而Mysql的排序是按照一定顺序排列的,因此这样的查询范围和原本的排序会完全不同,最终导致了索引的失效。
那既然双边通配符有如此问题,那么如果说我们要查询的是“2%”这样第一位为常量的数据,结果又会如何呢?
这次我们的type变为了range,说明b字段用到了范围查询。
也就是说,当我们的第一个字符是常量的情况下,b索引仍然可以进行范围排序以查询到我们想要的结果。
所以也有了sql优化中的金句:like避免使用双边通配符,原因是会导致索引的失效。
除此以外,!=,<>,is null,is not null还有or也会导致索引失效问题,其根本原因便是打破了索引原来的排列结构,导致索引无法被使用。
可以尝试如下sql语句,结果类似。
explain SELECT * from testtable where a!=1;
explain SELECT * from testtable where a<>1;
explain SELECT * from testtable where a is not null;
explain SELECT * from testtable where a=1 or b=2;
这就是索引失效需要注意的情况。