《mysql》9-mysql为什么会“选错”索引

mysql执行查询语句的时候,按照我们之前梳理的执行顺序,首先是

  1. 连接器,
  2. 查询缓存,命中之后返回
  3. 分析器,分析语法是否正确
  4. 优化器,优化执行语句,选择索引
  5. 执行器,执行器校验权限等。
  6. 执行引擎,具体的引擎

只有到了5这一步,才会真正的执行查询语句,这个过程非常的有意思,我们在第四步优化器的时候会选择当前查询语句依赖的索引。比如sql语句:

select a.name,a.age,a.address from table_a a where a.name like 'zhang%' and age = 28;

如果有两个索引 name和 age。按照索引name查询的话,需要查询数据行数2000条,按照age索引查询的话需要查询数据40000条,那么优化器会按照name索引查询。这样就会减少访问存储的次数,缩短执行时间。

在很少的的情况下,mysql会“选错”索引。比如下面的情况。


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;

delimiter ;;
create procedure idata()
begin
  declare i int;
  set i=1;
  while(i<=40000)do
    insert into t values(i, i, i);
    set i=i+1;
  end while;
end;;
delimiter ;
call idata();

新建一个表t,插入10万条数据,

然后继续执行:

用两个链接执行A和B

执行B的结果:

我们发现,当前执行没有选中索引。

如果我们使用强制索引,的时候

扫描二维码关注公众号,回复: 11938080 查看本文章

我们看到当前的执行结果比一开始的执行效果好,key使用的是a,rows扫描了10001条数据,比一开始默认执行扫描的条数33458要少很多。说明在B执行的时候,没有使用索引,而是使用了extra 的using where/全局扫描。为什么会这样呢。

这个地方就是优化器的逻辑问题了。优化器不仅决定启用哪条索引,还决定是否排序,是否新建临时表。但是上面的简单查询语句没有要排序和临时表的需求,那在选择索引的时候,靠什么指标呢。

  1. 索引基数,基数=抽样平均数=索引的数据页中选取N个数据页,计算几个数据页上索引的总数,然后/N得到平均数,然后*总页数。基数越大说明当前索引标记的数据越多,覆盖的范围越大。查询更精准。
  2. 扫描行数,当前索引查询的时候需要扫描的数据行数。
show index from t;-- 现实索引基数
explain select * from t where a between 10000 and 20000;-- 查看执行时扫描的行数rows

我们看到索引的基数 a,b,id都是一样的

那按照扫描次数看,三个都一样,所以在不必回表的情况下,选择主键索引比较稳妥,所以有我们默认执行的时候的结果。回归主键索引,全局扫描。出现这种情况后,说明当前的表的索引不太精准,我们可以执行

analyze table t

语句重新统计索引信息,然后执行

explain select * from t ;

这次结果就正确了。

为什么会造成上面一开始的情况呢,主要是我们在删除表数据和重新插入的时候,因为sessionA的可重复读的情况,索引都不会随着删除新增而重新统计造成的。所以我们强制重新统计.

索引在我们数据被该表了1/M之一的时候,会重新统计索引。在innodb_stats_persistent 为off时,M是10,N是20,在on时,M=16,N=8;

上面的例子中,索引又错了,因为在a索引扫描的话,可以值扫描1000条,b索引要扫描10001条。这样的结果就比较悬殊了。

如果偶尔出现这样的场景,我们可以这么做:

  1. 修改语句,改成explain select * from t where (a between 1 and 1000) and (b between 10000 and 20000) order by b,a limit 1;
  2. 强制使用索引 explain select * from t force index(a)  where (a between 1 and 1000) and (b between 10000 and 20000) order by b limit 1;
  3. 新建一个合适的索引,比如 (a,b)的联合索引,可以利用覆盖索引的优势,不必回表查询。

以上的案例是采用极客时间中丁齐老师的mysql课程里面的内容,大家可以到原文中仔细查看https://time.geekbang.org/column/article/71173

猜你喜欢

转载自blog.csdn.net/David_lou/article/details/108977508