美文网首页
10 - MySQL选错索引

10 - MySQL选错索引

作者: 天命_风流 | 来源:发表于2020-05-29 16:44 被阅读0次

关键字

索引选择

0.错误情况

首先,建立一个简单的表,表中有 a、b 两个字段,并且分别建立了索引:

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;

然后,我们往表 t 中插入 10 万行记录,取值按整数递增,即:(1,1,1),(2,2,2),(3,3,3) 直到 (100000,100000,100000):

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

接下来,我们分析一条语句:

mysql> select * from t where a between 10000 and 20000;
使用 explain 分析这条语句的执行情况: 10-正常执行情况.png

没错,这条语句的执行是符合预期的。下面,我们来看一个会出错的情况,我们在这个已经有数据的表中,再做如下操作:


10-出错执行流程.png
这时,session B 的 select * from t where a between 10000 and 20000 语句就不会选择 a 索引了,而是直接使用了全表扫描。

执行下面三条语句:

set long_query_time=0;
select * from t where a between 10000 and 20000; /*Q1*/
select * from t force index(a) where a between 10000 and 20000;/*Q2*/
  • 第一句,是将慢查询日志的阈值设置为 0,表示这个线程接下来的语句都会被记录入慢查询日志中;
  • 第二句,Q1 是 session B 原来的查询;
  • 第三句,Q2 是加了 force index(a) 来和 session B 原来的查询语句执行情况对比。
下图是他们生成的慢查询日志: 10-慢查询日志.png

可以看到,Q1 扫描了 10万 行,进行了全表扫描,Q2 使用了 a 索引扫描了 10001行。

为什么会这样呢?实际上,索引的选择是由优化器决定的,但是在某些特殊情况下,优化器会选择并非最优的方案。一般我们很难事先防范这种 bug ,但是你有必要理解其中的逻辑。

1.优化器的逻辑

优化器判断语句执行方式,会用到 扫描行数、数据排序 等。

1.1扫描行数

优化器选择索引的逻辑之一,就是计算需要扫描的行数。需要执行的行数越少,意味着消耗资源越少,优化器也就更加倾向选择这种操作。

在上面的例子中,影响优化器选择的最主要的原因就是扫描行数。实际上,MySQL 在执行一个语句之前是不可能知道它需要扫描多少行的,所以它需要使用表的统计信息来估算记录数

1.1.1索引基数
一个索引上有多少个不同的值,这称之为“基数”。一个索引的基数越大,索引越容易被区分。在 MySQL 中,使用抽样统计的方法获取索引的基数值。而索引的基数,可以通过 show index 查看: 10-show index.png

你会发现,即使抽样统计有误差,但是三个索引的索引基数是差不多的,所以,其实选错索引这件事,和索引基数的关系并不太大。

1.1.2预估扫描行数
既然索引错误与索引基数关系不大,那么我们不妨分析一下 Q1 和 Q2 这两条语句: 10-预估扫描行数.png

在图中,row 表示预计扫描的行数:

  • Q1 的结果是符合预期的,预估将扫描 10w+ 数据。
  • Q2 的结果就不对了,我们已经在最开始试过,该命令当时的 row 只有 10001 ,是这个误差误导了优化器的判断。

你可能会疑问,即使 Q2 的 row 错误,但是 3w+ 的值依然小于 10w+ 啊,为什么优化器依然选择了 10w+ 的索引方式呢?这里简单说一下:因为使用 a 索引涉及到回表操作,优化器将这部分内容也算了进去,综合来看,优化器认为使用主键索引更快。

所以,归根结底,MySQL 选错索引,主要原因是它错误的判断了扫描行数,至于为什么会有错误的扫描行数,这留作今天的思考题。

既然问题出在统计信息,那么我们可以使用 analyze table t ,重新统计 t 表的索引信息,执行效果如下: 10-analyze table.png

这次就对了。

1.2数据排序

同样是这个表,我们执行下面的语句:

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

很明显,

  • 我们知道 t 表中所有数据的内容,使用 a 索引是最快的,只需要扫描 a 的前 1000 个值,然后进行回表。
  • 而如果使用 b 索引,需要扫描 5w 行,再进行回表。

然而,MySQL 又选错了索引,如果不使用强制索引,MySQL 会使用 b 作为索引,而它的执行花费非常高。

为什么会这样呢?因为语句的末尾有 order by b,所以优化器认为使用 b 索引可以避免排序,而使用 a 的化还需要根据 b 再进行一次排序。这就导致了错误选择。

2.如何处理索引选择异常 & 总结

之前已经说过,索引选择异常非常复杂。实际上,在大多数情况下,优化器的选择都是正确的,只有在非常特殊的时候,才会遇到上面我们说的情况。因此,因为一个较小概率的事重建优化器的选择方案,是得不偿失的。

所以,在出现索引选择异常的时候,给你一些小的处理方法:

  • 第一种方法,使用force index强行选择索引:在你非常确定使用某个索引是最优选择的时候,你可以为 MySQL 决定选择哪个索引。
  • 第二种方法,使用analyze table重建表的统计信息。
  • 第三种方法,修改语句,引导 MySQL 使用我们期望的索引:在排序的例子中,把“order by b limit 1” 改成 “order by b,a limit 1” ,语义的逻辑是相同的。但是在这种引导下,MySQL 会使用 a 作为索引。但是,这样的方法并不通用,因为你无法确定 a和b 之间的关系。
  • 第四种方法,新建一个更适合的索引,来给优化器做选择,或者删掉误用的索引:当然,你要确定这个索引可以删除。

思考题

前面我们在构造第一个例子的过程中,通过 session A 的配合,让 session B 删除数据后又重新插入了一遍数据,然后就发现 explain 结果中,rows 字段从 10001 变成 37000 多。

而如果没有 session A 的配合,只是单独执行 delete from t 、call idata()、explain 这三句话,会看到 rows 字段其实还是 10000 左右。你可以自己验证一下这个结果。

这是什么原因?请你分析一下。

上期答案

上一篇文章的问题是,如果某次写入使用了 change buffer 机制,之后主机异常重启,是否会丢失 change buffer 和数据。

答案是不会丢失,虽然是只更新内存,但是在事务提交的时候,我们把 change buffer 的操作也记录到 redo log 里了,所以崩溃恢复的时候,change buffer 也能找回来。


以上就是本节内容,希望在人生路上,你能获得正确索引的指引。

注:本文章的主要内容来自我对极客时间app的《MySQL实战45讲》专栏的总结,我使用了大量的原文、代码和截图,如果想要了解具体内容,可以前往极客时间

相关文章

  • 10 - MySQL选错索引

    关键字 索引选择 0.错误情况 首先,建立一个简单的表,表中有 a、b 两个字段,并且分别建立了索引: 然后,我们...

  • 丁奇-MySQL实战读书笔记10

    MySQL为什么有时候会选错索引? 1:MySQL选错索引,啥意思? 我们认为使用K索引检索的速度会更快的,但是M...

  • mysql选错索引

    为什么会出现选错索引 优化器会判定当前执行的最优方式,主要包括考虑扫描的行数、是否使用临时表、是否需要排序等; 优...

  • mysql听讲(十)

    本文源自 极客网站 的某知名大佬讲mysql,整理一下心得和重点。 第10节 mysql为什么会选错索引 mys...

  • 为何MySQL会选错索引

    在实际的生产环境中,给MySQL的字段加索引让查询的效率更高,我们往往会给MySQL的表字段加索引。MySQL具体...

  • 10. MySQL选错索引bug, 基数(cardinality

    2个字段 a,b 上都有索引 t 中插入 10 万行记录,取值按整数递增,即:(1,1,1),(2,2,2),(3...

  • mysql竟然也会选错索引

    我们都知道一张表可以建很多索引,我们在执行一次查询时并没有执行使用哪个索引,是谁在帮助我们决定使用什么索引?是my...

  • 案例分析之mysql选错索引

    前言案例取自极客时间《mysql45讲》 案例 模拟执行器分析查询语句 场景复现 奇了怪了,此时没用索引,进行了全...

  • 【MySQL】10|MySQL为什么有时会选错索引?

    我们都知道在MySQL中一张表可以支持多个索引。但是我们在写sql的时候,并没有主动指定使用哪个索引,也就是说,使...

  • MySQL索引及查询优化书目录

    MySQL索引的原理之索引目的 MySQL索引的原理之索引原理 MySQL索引的原理之索引的类型 MySQL索引的...

网友评论

      本文标题:10 - MySQL选错索引

      本文链接:https://www.haomeiwen.com/subject/iuzxzhtx.html