MySQL优化器特性(二)MRR优化

俊达2年前技术文章1908

Index Range Scan

索引范围扫描的一般步骤:

1、根据where条件,从B+树定位到第一条记录。

2、从索引页子节点中获取到行号(rowid),根据rowid回表查询数据。

3、使用额外的where条件过滤记录,若满足条件,将记录返回给客户端。

不同的存储引擎,rowid和表的存储格式可能会有差异。比如在MyISAM中,rowid记录的是记录在文件中的偏移量,实际的表中,不需要额外存储rowid。在InnoDB中,rowid就是表的主键,表存储为聚簇索引。

index_range_scan.png


使用索引范围扫描(Index Range Scan)时,按顺序获取的记录ID(rowid)在表中是无序的,当有大量记录需要回表查询时,可能会产生大量随机IO,影响数据库性能。


针对这种场景,MySQL设计了DSMRR(Disk Sweep Multi-range Read)访问路径,在回表查询记录前,先将从索引中获取到的rowid存放在MRR buffer中,并按rowid进行排序,然后按rowid的顺序回表查询数据。


MRR优化的例子

满足一定的条件后,优化器才会使用MRR优化,出于演示的目的,我们给查询添加Hint。

测试表结构:

mysql> show create table tb\G
*************************** 1. row ***************************
       Table: tb
Create Table: CREATE TABLE `tb` (
  `a` int DEFAULT NULL,
  `b` int DEFAULT NULL,
  `c` int DEFAULT NULL,
  `id` int NOT NULL AUTO_INCREMENT,
  PRIMARY KEY (`id`),
  KEY `idx_c` (`c`),
  KEY `idx_ab` (`a`,`b`)
) ENGINE=InnoDB AUTO_INCREMENT=321 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1 row in set (0.00 sec)

MRR hint:

mysql> explain select /*+ MRR(tb) */ *  from tb where a < 2\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: tb
   partitions: NULL
         type: range
possible_keys: idx_ab
          key: idx_ab
      key_len: 5
          ref: NULL
         rows: 32
     filtered: 100.00
        Extra: Using index condition; Using MRR
1 row in set, 1 warning (0.00 sec)

Extra中显示“Using MRR”,说明采用了MRR优化。


不加Hint时,没有使用MRR优化:

mysql> explain select  *  from tb where a < 2\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: tb
   partitions: NULL
         type: range
possible_keys: idx_ab
          key: idx_ab
      key_len: 5
          ref: NULL
         rows: 32
     filtered: 100.00
        Extra: Using index condition
1 row in set, 1 warning (0.00 sec)


使用BKA hint也会使用MRR优化

mysql> explain select /*+ BKA(tb) */ *  from tb where a < 2\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: tb
   partitions: NULL
         type: range
possible_keys: idx_ab
          key: idx_ab
      key_len: 5
          ref: NULL
         rows: 32
     filtered: 100.00
        Extra: Using index condition; Using MRR
1 row in set, 1 warning (0.01 sec)


MRR执行过程

1、根据where条件,定位索引range。

2、获取rowid,先存放到MRR buffer中。

3、根据rowid对MRR buffer进行排序。

4、按rowid的顺序回表获取数据。

5、根据where条件中的额外条件过滤数据,若满足条件,将数据返回给客户端。

按rowid的顺序回表获取数据,可能能在一定程度上提升数据库缓存的命中率,减少磁盘随机IO。


mrr-buffer.jpg


使用MRR的条件

查询需要满足以下几个条件,优化器才会使用MRR优化:

1、使用了索引范围执行计划(range),非覆盖索引,非Primary Key。如果查询本身没有使用索引范围扫描的执行计划,就无法使用MRR优化。如果查询用了覆盖索引,不需要回表,或者是在InnoDB的主键上进行范围扫描,都不需要使用MRR优化。

2、表占用的空间超过一定大小时,才会使用MRR优化。对于InnoDB,表的大小超过InnoDB buffer pool,对于MyISAM,表的大小超过100M,优化器才会考虑使用MRR优化。

3、range查询预估需要访问的记录数超过一定数量,才会考虑使用MRR优化。如果预估需要访问的记录数不多,则不会考虑使用MRR优化。8.0.32版本中这个值是50。

4、optimizer_switch,开启MRR。默认情况下mrr和mrr_cost_based都是开启的。

5、如果optimizer_switch mrr_cost_based=on,则优化器会评估MRR执行计划的成本,只有当MRR的成本比默认range访问的成本更低时,才会使用MRR优化。

6、如果给查询添加MRR或BKA提示,则只需要满足条件1就可以使用MRR优化。当然一般情况下不建议使用这几个Hint。让优化器自动选择就可以了。


如何确定MRR buffer大小

MRR buffer大小的上限由参数read_rnd_buffer_size决定。如果优化器评估出来range需要访问的记录数较小,则实际分配的buffer可能会比read_rnd_buffer_size小,会按需分配buffer(记录数 * 单条ROWID占用的空间)。


相关文章

MySQL优化器特性(四)表关联之BNL(Block Nested Loop)和Hash Join

MySQL优化器特性(四)表关联之BNL(Block Nested Loop)和Hash Join

什么是BNLMySQL表关联时,如果关联条件上没有合适的索引,则join时,对于驱动表的每一条记录,都需要全表扫描被驱动表。如果驱动表有多条数据,则需要多次全表扫描被驱动表,查询性能很差。对于这种情况...

MySQL优化器特性(一)IN和Exists(semijoin)子查询优化策略

这篇文章中的SQL和执行计划在mysql 8.0.31环境下进行测试。测试的表结构和数据:表结构mysql> show create table tp\G...

MySQL优化器特性(八)索引范围扫描成本计算

MySQL优化器特性(八)索引范围扫描成本计算

range执行计划中的range表示索引范围扫描。索引范围扫描的执行过程大致如下:1、根据where条件中索引字段的条件,定位到索引结构中的第一条满足条件的记录。2、根据索引中记录的rowid,到表中...

MySQL优化器特性(七)成本估算常数

成本估算常数表示执行一些MySQL基础操作时的成本,如读取一个页面,创建一个临时表,比较一条记录,解析一行记录等操作。mysql.engine_cost和mysql.server_cost表分别记录存...

 MySQL优化器特性(九)行数评估

MySQL优化器特性(九)行数评估

查询的行数在成本计算中起了很重要的作用:1、row_evaluate_cost和行数直接相关2、需要访问多少索引页面,和行数直接相关。根据页面大小和平均索引条目长度计算每个索引页面的记录数,根据记录数...

MySQL优化器特性(六)表扫描成本计算

全表扫描成本使用optimizer_trace,或者使用explain format=tree, 或者explain format=json,可以查看查询的costmysql> exp...

发表评论    

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。