在书写SQL语句的时候,可以明确指定使用哪个索引,但是如果不指定的时候,使用哪个索引是由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;

上面有三个字段,然后往表上插入10万行记录,值依次递增,从1,1,1到100000,100000,100000

然后分析一个SQL语句

select * from t where a between 10000 and 20000;

然后我们使用

explain来查看mysql如何执行的

图片

使用了索引a

这说明mysql的执行并没有问题

然后我们执行如下的操作

图片

在Session A中,直接开启了一个事务A,但是没有任何操作,进行了commit

在Session B 中执行了如下语句

删除整个表

然后重新存储

然后进行查询

这时候如果通过slow log 查看对应的执行情况

会直接发现进行了一次全表扫描,这时候需要一个对比,我们再次编写一个Sql语句

为何进行了一次全表扫描呢?

也就是为何没有选择索引a呢?

这是因为Mysql在选择索引来执行语句之前,会进行判断一下需要扫描的行数

MySql并不能知道满足条件的行数,只能进行相关的估算

如何进行的估算,也就是MySql存储在索引上的区分度,亦可以称为 基数 cardinality,基数越大,索引的区分度越好

图片

对于这个基数的获取呢?

采用了采样统计的方法

就是InnoDB选择N个数据页,然后统计不同值,得到一个平均值,然后乘以索引的页面数

一般来说,也会从索引树上拿取这个基数值

但是MySql是会发生更新的,于是在更新超过1/M行后,进行一次重新统计

这个值可以设置

可以通过 innodb_stats_persistent来设置,可以分为 on和off

on的时候,统计信息放在持久化存储中,默认N是20,M是10

off的时候,统计信息值放在内存中,默认N是8,M是16

但是 M还是N都不准

但是MySql统计索引虽然不是非常准确,但并非是选错索引的唯一原因

如果我们使用如下的explain来进行测试的话

图片

会发现,如果强制使用index a,也只需要37000行

为啥放着37000行的执行不用,而是使用了100000行的执行呢?.

这可能是MySql预计了,如果使用了索引a,还需要进行回表扫描,这就导致优化器认为直接扫描主键索引会更加快,但是实际上它的选择是错误的

那么如何重新让索引回到正轨呢?

可以使用analyze table t命令,来重新统计索引信息,看一下执行效果

图片

还有就是一种索引的选错

Select * from t where (a between 1 and 1000) and (b between 50000 and 100000) order by b limit 1;

图片

如果是索引A,那么是扫描索引a的前1000个值,然后回表找b,扫描1000行

如果是索引B,那么是扫描索引b的最后50001值,然后在回表判断,扫描500001行

这样看,是索引A的执行快

图片

但是还是选择了索引B,这是为何?

当然可以使用force index来进行优化解决,但是并不建议这么做,我们仍需要知道为啥innodb选择了这个索引

1.我们使用了order by b,这样导致了优化器选择了索引b,因为索引b来进行查找的话,就无需排序了

2.当然,如果想要优化,可以将order by b改为了 order by b,a limit 1,让索引进行竞争,从而引导MySql选择正确的数值

3.最好的方式是建立一个更加合适的索引,来让优化器更加方便的去使用,或者删除诱导的索引

往往直接删除一个索引会更加的简单,有时候索引就是多余了,没必要去维护他

发表评论

邮箱地址不会被公开。 必填项已用*标注