你可能会感觉很奇怪,索引不是优化SQL用的吗?为什么优化SQL要删掉索引,别急,先看这个实验。
创建一个表
CREATE TABLE `t`(`id` int(11) NOT NULL,`a` int(11) DEFAULT NULL,`b` int(11) DEFAULT NULL,PRIMARY KEY (`id`),KEY `a` (`a`),KEY `b` (`b`)) ENGINE=InnoDB;
插入10w行数据
delimiter //create procedure idata()begin declare i int;set i=1;while(i<=100000)doinsert into t values(i, i, i);set i=i+1;end while;end//delimiter ;
首先我们来看,该查询使用了range扫描方式,也就是使用了索引a的范围扫描,这个很容易理解到,mysql的优化器选择了a索引

再来看这条
explain select * from t where (a between 1 and 1000) and(b between 50000 and 100000) order by b limit 1;
那么优化器该怎么选择索引呢?
如果使用索引 a 进行查询,那么就是扫描索引 a 的前 1000 个值,然后取到对应的 id,再到主键索引上去查出每一行,然后根据字段 b 来过滤。显然这样需要扫描 1000 行。
如果使用索引 b 进行查询,那么就是扫描索引 b 的最后 50001 个值,与上面的执行过程相同,也是需要回到主键索引上取值再判断,所以需要扫描 50001 行。
你一定会想,如果使用索引 a 的话,执行速度明显会快很多。那么,下面我们就来看看到底是不是这么一回事儿。

结果却不尽人意,优化器选错了索引,选择了索引 b 而 rows 字段显示需要扫描的行数是 50155。(这里需要解释下,扫描行数的值是采样估算的,所以一般不是精准的)
很明显,优化器选错了索引,那我们如何修正呢
(1)可以采用force index强制使用索引a

可以看到,这次扫描的数据只有1000行了。
(2)为什么优化器选择了b?
之前优化器选择使用索引 b,是因为它认为使用索引 b 可以避免排序(b 本身是索引,已经是有序的了,如果选择索引 b 的话,不需要再做排序,只需要遍历),所以即使扫描行数多,也判定为代价更小。

这样的sql,也正确选择了索引
(3)删除索引b
对于生产环境中,可以结合业务分析,如果该索引没有有利于业务,还不如删掉它。
其实这也是mysql优化器的一个bug,索引的选择,是优化器的工作,之前我也遇到过很多优化器的坑。
优化器选择索引的目的,是找到一个最优的执行方案,并用最小的代价去执行语句。在数据库里面,扫描行数是影响执行代价的因素之一。扫描的行数越少,意味着访问磁盘数据的次数越少,消耗的 CPU 资源越少。当然,扫描行数并不是唯一的判断标准,优化器还会结合是否使用临时表、是否排序等因素进行综合判断。
在这里,我们优化器就是错误判断了排序因素。
在解决今天遇到这句SQL上,最加选择是第3种方法,因为第一种方法,程序员并不知道项目到了线上后的结果,一般都是出bug了才会修改,并且这种语法并不适合将来万一迁移数据库。第二种方法,并没有结合实际,只是主观的修复bug而拼写,不实用。因此,遇到这种情况,最佳选择,结合实际,和开发协商,删除掉“不好用”的索引。
今天的内容就到这里了,另外请大家帮个小忙,帮忙扫描下图,投个票。谢谢大家,哈哈。





