• MySQL为什么有时候会选错索引?


    案例:

     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;

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

    1)10万条记录是怎么插进去的?

    • 存储过程

    2)什么是存储过程?

    • 其实就是java中的函数

    3)插入10万条记录的存储过程怎么写?

     delimiter ;; //告诉mysql,该段命令是否结束
     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();

    4)执行select * from t where a between 10000 and 20000语句,选择是索引是谁?

    • a

    5)执行下面图中的流程,那又是选择谁做索引?

    不会再选择a做索引了。

    5.1)为什么mysql会选错索引,不选择a做索引了?

    • MySQL 在判断扫描行数的时候出问题了

    5.1.1)扫描行数是怎么判断的?

    • 根据统计信息来估算。

    5.1.2)那么这个统计信息是啥呢?

    • 索引的“区分度“:一个索引上不同的值越多,这个索引的区分度就越好。一个索引上不同的值的个数,我们称之为“基数”。

    5.1.3)MySQL 是怎样得到索引的基数的呢?

    • 采样统计(选N 个数据页,统计这些页面上的不同值,得到一个平均值,然后乘以这个索引的页面数)

    5.1.4)为什么要采样统计?

    • 整张表取出来一行行统计,代价太高

    5.1.5)索引统计信息是固定的吗?

    • 不是,数据表变,他也跟着变,1/M就变,比如10万条,M就是10,那更新了10000条数据的时候就重新统计索引信息。

    5.1.6)索引信息是放在哪里的?

    • innodb_stats_persistent=on,放在磁盘。N为20,M为10.

    • innodb_stats_persistent=off,放在内存。N为8,M为16。

    索引统计只是一个输入,对于一个具体的语句来说,优化器还要判断,执行这个语句本身要扫描多少行。

    5.1.7)优化器预估的,Q1和Q2的扫描行数是多少?

     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*/

    • Q1 的结果还是符合预期的,rows 的值是 104620。Q2 的 rows 值是 37116,而图 1 中我们用 explain 命令看到的 rows 是只有 10001 行,是这个偏差误导了优化器的判断。

    6)我们上面说不再选择a作为索引,优化器为什么放着扫描 37000 行的执行计划不用,却选择了扫描行数是 100000 的执行计划呢?

    • 用索引a我每次都得回表一次。

    • 如果选择扫描 10 万行,是直接在主键索引上扫描的,没有额外的代价

    • 优化器会估算这两个选择的代价,从结果看来,优化器认为直接扫描主键索引更快。当然,从执行时间看来,这个选择并不是最优的。

    7)mysql选错索引的根本原因是什么?

    • 没能准确地判断出扫描行数

    8)当发现mysql选错索引了我们应该怎么办?

    • 使用analyze table t 命令,可以用来重新统计索引信息

    9)假设现在有如下语句,假如你来选择索引,你会怎样选择?

    • 第一直觉当然是选a

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

    先来看一下 a、b 这两个索引的结构图

    • 如果使用索引 a 进行查询,那么就是扫描索引 a 的前 1000 个值,然后取到对应的 id,再到主键索引上去查出每一行,然后根据字段 b 来过滤。显然这样需要扫描 1000 行。

    • 如果使用索引 b 进行查询,那么就是扫描索引 b 的最后 50001 个值,与上面的执行过程相同,也是需要回到主键索引上取值再判断,所以需要扫描 50001 行。

    10)实际上上面的语句mysql选择谁做索引?

    竟然选择了b做索引。

    11)为什么会选择b来做索引?

    • 又选错了

    12)为什么又选错了?

    • 扫描行数的估计值依然不准确;

     

    大多数时候优化器都能找到正确的索引,但偶尔你还是会碰到我们上面举例的这两种情况:原本可以执行得很快的 SQL 语句,执行速度却比你预期的慢很多

    13)选错索引我们应该怎么办呢?

    • 采用 force index 强行选择一个索引

    • 在数据库内部来解决

    14)用 force index有什么缺点?

    • 不优美

    • 如果索引改了名字,这个语句也得改

    • 如果以后迁移到别的数据库的话,这个语法还可能会不兼容。

    15)在数据库里面该怎样解决呢?

    15.1)改变自己:

    • 优化器放弃了使用索引 a,说明 a 还不够合适。我们可以考虑修改语句,引导 MySQL 使用我们期望的索引。

    • 把“order by b limit 1” 改成 “order by b,a limit 1”

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

    • 现在 order by b,a 这种写法,要求按照 b,a 排序,就意味着使用这两个索引都需要排序。因此,扫描行数成了影响决策的主要条件,于是此时优化器选了只需要扫描 1000 行的索引 a。

    15.2)麻痹优化器

     select * from  (select * from t where (a between 1 and 1000)  and (b between 50000 and 100000) order by b limit 100)alias limit 1;
    • 用 limit 100 让优化器意识到,使用 b 索引代价是很高的。其实是我们根据数据特征诱导了一下优化器,也不具备通用性

    15.3)新建一个更合适的索引,或删掉误用的索引。

     

    16)前面我们在构造第一个例子的过程中,通过 session A 的配合,让 session B 删除数据后又重新插入了一遍数据,然后就发现 explain 结果中,rows 字段从 10001 变成 37000 多。而如果没有 session A 的配合,只是单独执行 delete from t 、call idata()、explain 这三句话,会看到 rows 字段其实还是 10000 左右。你可以自己验证一下这个结果。这是什么原因呢?

    • session A开启了一致性读,目的为了保证session A的可重复读,insert只能 另起炉灶,不能占用delete的空间。所以出现的情况就是delete虽然删除了,但是未释放空间,insert又增加了空间。导致统计信息有误

  • 相关阅读:
    Dynamic导出解决方案修改其XML信息
    子网格
    官方文档
    ADFS登录页面自定义
    ADFS设置Tokn生命周期
    特征工程
    Pandas
    分类决策树
    Python基本知识
    机器学习的基本概念
  • 原文地址:https://www.cnblogs.com/YXBLOGXYY/p/15966692.html
Copyright © 2020-2023  润新知