一.优化器的选择逻辑
建表语句
CREATE TABLE `t` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`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)do
insert into t (`a`,`b`) values(i, i);
set i=i+1;
end while;
end;;
delimiter ;
call idata();
接着执行SQL语句
select * from t where a between 10000 and 20000;
由于a上有普通索引,索引优化器肯定会选择使用a索引,与explain一致
但是如果此时有另外一个事务开启了一致性视图,如下所示
session A在 session B 之前开启了一致性视图,并且没有提交,那么 此时的 undo log 不能被清理,虽然此时 session B 做了删除操作,但数据不会被真正的删除。因此,在session B 再次插入10W条数据后 此时 undo log 保存了 20W的版本信息,当前数据页的数据页无法被覆盖,只能用另外的数据页来存储数据
而此时的session B 的分析结果将会出现扫描 10W行的情况,走了全表扫描,并没有使用到索引 a
导致此现象产生的原因 是由于受一致性视图的影响,导致计算索引的区分度出现了偏差,预估了错误的扫描行锁,而索引a 非主键索引,还需要回表进行一次查询操作,多一次IO操作的代价使MySQL的优化器觉得不如走全表扫描
当发现MySQL出现明细的统计数据行数出现异常后,我们可以执行以下命令重新统计索引信息,解决采样导致的扫描行数出错的问题
analyze table t
二.如何解决MySQL选错索引
基于上述的建表语句与数据,当我们执行下面的查询语句时,
explain select * from t where (a between 1 and 1000) and (b between 50000 and 100000) order by b limit 1;
预期时扫描1001行,但实际上MySQL扫描了50191行,远远超出我们的预期,这是由于 oder by 和 limitd的影响:
- 因为有 order by b,优化器认为走索引 b 可以避免排序;
- 又有 limit 1,优化器认为只要找到了 1 条满足条件的记录,索引 b 的遍历就可以提前终止,虽然可能要遍历 50001 条记录,但是优化器认为这是值得冒险的事,所以决定了走索引 b;
强制使用索引
使用 force index(a) 语句后,强制使用索引 a,这时候发现扫描的行数只有1000了,符合我们的预期,MySQL不得不作出正确的选择
explain select * from t force index(a) where (a between 1 and 1000) and (b between 50000 and 100000) order by b limit 1
修改SQL语句
如果我们能让MySQL判断出,使用索引b的代价比索引a大,那么MySQL就能选择到正确的索引
所以,我们可以
- 干扰limit 判断
explain select * from ( select * from t where (a between 1 and 1000) and (b between 50000 and 100000) order by b limit 100) tt limit 1
最初的SQL语句因为b不用排序,又有limit 1,从5w里只要找到一条就可以返回了,如果选择a,因为要排序,就要扫完1000条,然后才能排序,这成本明显太大,所以选择了b。但如果是limit 100,选择b,虽然不用排序,但找到第一条记录后,还要向后查询,看后面有没有满足条件的100个记录,从5w中找100个的成本就大于从1000找100个的成本了,所以选择a。其实limit 20就会选择a了
-
干扰order by判断
explain select * from t where (a between 1 and 1000) and (b between 50000 and 100000) order by b,a limit 1
要求按照b,a排序,无论选择b索引还是a索引,都只需要再将另外一个字段排序(个人认为索引b已经对b排好序,再对a排序;索引a已经对a排好序,再对b排序成(b,a)。这两者数据库引擎按照同样的排序算法去排序,前者成本较小,但是数据库引擎并不能感知得了),所以扫描行数成了影响决策的主要条件。