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

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 用索引再回表(用索引下推)

10. MySQL选错索引bug, 基数(cardinality)_第1张图片

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

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

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

慢查询日志的记录:


10. MySQL选错索引bug, 基数(cardinality)_第2张图片

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

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

10. MySQL选错索引bug, 基数(cardinality)_第3张图片

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

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

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

10. MySQL选错索引bug, 基数(cardinality)_第4张图片
看下mysql 优化器认为要各扫描多少行

10. MySQL选错索引bug, 基数(cardinality)_第5张图片

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

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

为什么会错

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

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

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

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

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

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

show index 看到一个索引的基数

索引基数
10. MySQL选错索引bug, 基数(cardinality)_第6张图片
主键,a,b 应该都是十万

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

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

你可能感兴趣的:(10. MySQL选错索引bug, 基数(cardinality))