美文网首页
10. MySQL选错索引bug, 基数(cardinality

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

作者: 胖达_4b7e | 来源:发表于2019-02-13 23:33 被阅读0次

    2个字段 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)。
    插入的存储过程idata如下:

    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();
    

    查一万条

    explain select * from t where a between 10000 and 20000;
    
    

    select_type: SIMPLE(简单SELECT,不使用UNION或子查询等)
    partitions: 分区表命中的分区情况。非分区表该字段为空(null)
    rows: 估算的找到所需的记录所需要读取的行数 10001 行
    filtered : 存储引擎返回的数据在server层过滤后,剩下多少满足查询的记录数量的比例,注意是百分比,不是具体记录数. 100.0 说明都要
    Extra: Using index condition 用索引再回表(用索引下推)


    左边开启一个事务,再提交
    右边把表清空 再用存储过程重新插入十万条数据

    这样再查询, 会自动不用索引!
    试试
    所有查询都记入慢查询日志:

    set long_query_time=0;
    

    Q1 自动用索引, Q2手动强制用索引a

    select * from t where a between 10000 and 20000; /*Q1*/
    select * from t force index(a) where a between 10000 and 20000;/*Q2*/
    

    慢查询日志的记录:



    可以看到Q1 查了全部十万条, 完全不像用到索引了

    是 优化器 的扫描行数估计错了


    优化器在执行器调用引擎API之前用到

    优化器会选择它觉得扫描行数最少的索引, 所以 一定是在 判断扫描行数的时候 出错了

    扫描的行数越少,意味着访问磁盘数据的次数越少,消耗的 CPU 资源越少。
    扫描行数并不是唯一的判断标准,优化器还会结合是否使用临时表、是否排序等因素进行综合判断。
    只是,这个简单的查询语句并没有涉及到临时表和排序,所以 MySQL 选错索引肯定是在判断扫描行数的时候出问题了。

    看下mysql 优化器认为要各扫描多少行

    Q1 是对的 十万多条
    Q2 错了, 应该和之前一样是一万多条 ,不该是3万多条,
    如果真是3万多条,用a 索引以后, 还要回表去查主键, 判断Q1为更优的方案是合理的

    既然是统计信息不对,那就修正。analyze table t 命令 就行了

    为什么会错

    delete 语句删掉了所有的数据,然后再通过 call idata() 插入了 10 万行数据,看上去是覆盖了原来的 10 万行。

    但是,session A 开启了事务并没有提交,所以之前插入的 10 万行数据是不能删除的。

    之前的数据每一行数据都有两个版本,旧版本是 delete 之前的数据,新版本是标记为 deleted 的数据。
    这样,索引 a 上的数据其实就有两份。

    扫描行数根据抽样估算的区分度-----基数 来计算

    满足这个条件的记录有多少条:
    只能根据 基数 估算记录数。

    这个统计信息就是索引的区分度
    一个索引上不同的值的个数,我们称之为基数(cardinality)
    基数越大,索引的区分度越好。

    show index 看到一个索引的基数

    索引基数 主键,a,b 应该都是十万

    不准确, 但是差不多, 所以不是基数统计错误的缘故

    基数是“采样统计”得到的:
    InnoDB 默认会选择 N 个数据页,统计这些页面上的不同值,得到一个平均值
    当变更的数据行数超过 1/M 的时候,会自动触发重新做一次索引统计。

    相关文章

      网友评论

          本文标题:10. MySQL选错索引bug, 基数(cardinality

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