Mysql选错索引分析

169 阅读3分钟
  • 准备工作:
  1. 创建一张表

    create TABLE `test_table2`(
    	`id` int(20) NOT NULL AUTO_INCREMENT COMMENT '自增主键',
    	`a` int(11) NOT NULL COMMENT '测试字段a',
    	`b` int(11) NOT NULL COMMENT '测试字段b' ,
    	primary key (`id`),
    	key `a` (`a`),
    	key `b` (`b`)
    	)ENGINE=InnoDB DEFAULT CHARSET=utf8m64 COMMIT='测试表';
    
  2. 开启慢查询日志

    set global long_query_time = 0;
    set global slow_query_log='ON';
    set global slow_query_log_file='/data/instance-1-slow.log';
    //验证
    select sleep(2);
    
  3. 使用存储过程生成100000条数据

    delimiter ;;
    create procedure idata1()
    begin 
    	declare i int; 
    	set i=1; 
    	while(i<=100000)do 
    		insert into `test_table2`(`a`,`b`) values(i, i); 
    		set i=i+1; 
    		end while;
    	end;;
    	delimiter ;
    	call idata1();
    
  4. 通过两个sessionA,sessionB实现如下操作

    select @@global.tx_isolation,@@tx_isolation,version(),"session A";
    
    start TRANSACTION with consistent snapshot;
    
    select @@global.tx_isolation,@@tx_isolation,version(),"session B";
    
    delete from `test_table2`;
    
    call idata1();
    
    explain select * from `test_table2` where a between 10000 and 20000;
    //Q1
    select * from `test_table2` where a between 10000 and 20000;
    //Q2
    select * from `test_table2` force index(a) where a between 10000 and 20000;
    
    commit;
    
  5. 结果分析
    运行结果中Q1扫描了10万行,是全表扫描,执行时间是40毫秒;Q2扫描了10001行,执行了18毫秒,也就是优化器选错了索引,导致更长的执行时间。这个例子对应的是平常不断删除历史数据和新增数据的场景。

  • 优化器逻辑

    • 作用之一:选择索引,即找到一个最优的执行方案,并用最小的代价去执行语句。

    • 判断扫描行数方法:Mysql在真正开始执行语句之前,并不能准确地知道满足这个条件的记录有多少条,而只能根据统计信息来估算记录书,这个统计信息就是索引的区分度。

    • 基数:一个索引上不同的值的个数。基数越大,索引的区分度越好。

    • 使用show index命令可查看索引基数

    • Mysql得到索引基数的方法是采样统计(整张表一行行统计虽准确但代价太高)

      • 过程:采样统计的时候,InnoDB默认选择N个数据页,统计这些页面上不同的值,得到一个平均值,乘以这个索引的页面数,就得到了该索引的基数。数据表会持续更新,索引统计信息也不会固定不变。当变更数据超过1/M时,会自动触发重新做一次索引统计。
      • Mysql中有两种存储索引统计的方式,通过innodb_stats_persistent的值来选择:
        • 设置为on时,表示统计信息会持久化存储,默认N是20,M是10
        • 设置为off时,表示统计信息只存储于内存中,默认N是8,M是16
    • 之前问题的主要原因是Mysql没能准确判断出扫描行数

    • 解决方法:使用analyze table命令重新统计索引信息来修正,explain可查看修正结果

  • 索引选择异常和处理

    1. 采用force index强行选择一个索引
    2. 修改语句引导Mysql使用期望的索引
    3. 再有些场景下,可新建一个更合适的索引,提供优化器做选择,或者删除误用的索引