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之前用到
优化器会选择它觉得扫描行数最少的索引, 所以 一定是在 判断扫描行数的时候 出错了
看下mysql 优化器认为要各扫描多少行扫描的行数越少,意味着访问磁盘数据的次数越少,消耗的 CPU 资源越少。
扫描行数并不是唯一的判断标准,优化器还会结合是否使用临时表、是否排序等因素进行综合判断。
只是,这个简单的查询语句并没有涉及到临时表和排序,所以 MySQL 选错索引肯定是在判断扫描行数的时候出问题了。
Q1 是对的 十万多条
Q2 错了, 应该和之前一样是一万多条 ,不该是3万多条,
如果真是3万多条,用a 索引以后, 还要回表去查主键, 判断Q1为更优的方案是合理的
既然是统计信息不对,那就修正。analyze table t
命令 就行了
为什么会错
delete 语句删掉了所有的数据,然后再通过 call idata() 插入了 10 万行数据,看上去是覆盖了原来的 10 万行。
但是,session A 开启了事务并没有提交,所以之前插入的 10 万行数据是不能删除的。
之前的数据每一行数据都有两个版本,旧版本是 delete 之前的数据,新版本是标记为 deleted 的数据。
这样,索引 a 上的数据其实就有两份。
扫描行数根据抽样估算的区分度-----基数 来计算
满足这个条件的记录有多少条:
只能根据 基数 估算记录数。
这个统计信息就是索引的区分度。
一个索引上不同的值的个数,我们称之为基数(cardinality)。
基数越大,索引的区分度越好。
show index
看到一个索引的基数
不准确, 但是差不多, 所以不是基数统计错误的缘故
基数是“采样统计”得到的:
InnoDB 默认会选择 N 个数据页,统计这些页面上的不同值,得到一个平均值
当变更的数据行数超过 1/M 的时候,会自动触发重新做一次索引统计。
网友评论