MySQL 优化器 MRR

什么是 MRR

MRR 的全称是 Multi-Range Read Optimization,是优化器将随机 IO 转化为顺序 IO 以降低查询过程中 IO 开销的一种手段,咱们对比一下 mrr=on & mrr=off 时的执行计划:

其中表结构如下:

mysql> show create table t1\G
*************************** 1. row ***************************Table: t1
Create Table: CREATE TABLE `t1` (`id` int(11) NOT NULL AUTO_INCREMENT,`a` int(11) DEFAULT NULL,`b` int(11) DEFAULT NULL,`c` int(11) DEFAULT NULL,PRIMARY KEY (`id`),KEY `mrrx` (`a`,`b`),KEY `xx` (`c`)
) ENGINE=MyISAM AUTO_INCREMENT=11 DEFAULT CHARSET=latin1
1 row in set (0.00 sec)

操作如下:

mysql> set optimizer_switch='mrr=off';
Query OK, 0 rows affected (0.00 sec)mysql>  explain select * from test.t1 where (a between 1 and 10) and (c between 9 and 10) ;
+----+-------------+-------+-------+---------------+------+---------+------+------+------------------------------------+
| id | select_type | table | type  | possible_keys | key  | key_len | ref  | rows | Extra                              |
+----+-------------+-------+-------+---------------+------+---------+------+------+------------------------------------+
|  1 | SIMPLE      | t1    | range | mrrx,xx       | xx   | 5       | NULL |    2 | Using index condition; Using where |
+----+-------------+-------+-------+---------------+------+---------+------+------+------------------------------------+
1 row in set (0.00 sec)

当把 MRR 关掉的情况下,执行计划使用的是索引 xx(c),即从索引 xx 上读取一条数据后回表,取回该主键的完整数据,当数据较多且比较分散的情况下会有比较多的随机 IO, 导致性能低下,我们将 MRR 打开,执行以下操作:

mysql> set optimizer_switch='mrr=on';
Query OK, 0 rows affected (0.00 sec)mysql>  explain select * from test.t1 where (a between 1 and 10) and (c between 9 and 10) ;
+----+-------------+-------+-------+---------------+------+---------+------+------+-----------------------------------------------+
| id | select_type | table | type  | possible_keys | key  | key_len | ref  | rows | Extra                                         |
+----+-------------+-------+-------+---------------+------+---------+------+------+-----------------------------------------------+
|  1 | SIMPLE      | t1    | range | mrrx,xx       | xx   | 5       | NULL |    2 | Using index condition; Using where; Using MRR |
+----+-------------+-------+-------+---------------+------+---------+------+------+-----------------------------------------------+
1 row in set (0.00 sec)

可以看到 extra 的输出中多了 “Using MRR” 信息,即使用了 MRR Optimization IO 层面进行了优化,减少 IO 方面的开销,更详细的说明可以参考这里。

MRR 原理

在不使用 MRR 时,优化器需要根据二级索引返回的记录来进行“回表”,这个过程一般会有较多的随机 IO, 使用 MRR 时,SQL 语句的执行过程是这样的:

  • 优化器将二级索引查询到的记录放到一块缓冲区中;
  • 如果二级索引扫描到文件的末尾或者缓冲区已满,则使用快速排序对缓冲区中的内容按照主键进行排序;
  • 用户线程调用 MRR 接口取 cluster index,然后根据cluster index 取行数据;
  • 当根据缓冲区中的 cluster index 取完数据,则继续调用过程 2) 3),直至扫描结束;

通过上述过程,优化器将二级索引随机的 IO 进行排序,转化为主键的有序排列,从而实现了随机 IO 到顺序 IO 的转化,提升性能。

MRR 源码分析

首先,咱们来看一下 mrr 相对应的内存结构:

class DsMrr_impl
{...handler *h;TABLE *table; /* Always equal to h->table */
private:/* Secondary handler object.  It is used for scanning the index */handler *h2;/* Buffer to store rowids, or (rowid, range_id) pairs */uchar *rowids_buf;uchar *rowids_buf_cur;   /* Current position when reading/writing */uchar *rowids_buf_last;  /* When reading: end of used buffer space */uchar *rowids_buf_end;   /* End of the buffer */bool dsmrr_eof; /* TRUE <=> We have reached EOF when reading index tuples */int dsmrr_init(handler *h, RANGE_SEQ_IF *seq_funcs, void *seq_init_param,uint n_ranges, uint mode, HANDLER_BUFFER *buf);….int dsmrr_fill_buffer();int dsmrr_next(char **range_info);bool get_disk_sweep_mrr_cost(uint keynr, ha_rows rows, uint flags, uint *buffer_size, Cost_estimate *cost);….
}

简单说明:h2 指的是 MRR 使用的 second index 或主键索引, h 是指利用 h2 返回的主建来查询的句柄,rowids_buf 是 MRR 执行过程中存储有序主键的缓存区,大小由 MySQL 的变量 read_rnd_buffer_size 设置,下面我们结合程序的执行过程来看一下源码。

1)MRR 中有序主建的收集过程

优化器对查询语句的条件进行分析并选择合适的二级索引,并对二级索引的条件进行筛选拼装成 DYNAMIC_ARRAY ranges,在执行的时候将 ranges 传入初始化函数 ha_myisam::multi_range_read_init ,继而会调用 dsmrr_fill_buffer 函数,在dsmrr_fill_buffer中会使用二级索引的句柄查找符合 ranges 的数据并添加至 rowids_buf 中,在扫描结束或缓冲区满的时候会对 rowids_buf 进行快速排序,详细过程可以参考函数:dsmrr_fill_buffer,其调用堆栈下:

 #0  DsMrr_impl::dsmrr_fill_buffer (this=0x2aab0000cf00)#1  0x00000000006e49dd in DsMrr_impl::dsmrr_init(...)#2  0x00000000017d35e4 in ha_myisam::multi_range_read_init(...)#3  0x0000000000d134c6 in QUICK_RANGE_SELECT::reset (this=0x2aab00014070)#4  0x00000000009a266f in join_init_read_record (tab=0x2aab0000f5b8)#5  0x000000000099d6d4 in sub_select#6  0x000000000099c914 in do_select (join=0x2aab000064b0)#7  0x00000000009982f8 in JOIN::exec (this=0x2aab000064b0)#8  0x0000000000a5bd7c in mysql_execute_select........

2)MRR 中主建缓冲区的使用过程

物理执行阶段,调用 ha_myisam::multi_range_read_next,在使用 MRR 的情况下会从过程1)中收集的有序主键的缓冲区取主键,然后再调用引擎层的 rnd_pos 直接找到数据,其中使用 mrr 的调用堆栈如下:

 #0  DsMrr_impl::dsmrr_next (this=0x2aab0000cf00, range_info=0x2aaafc03de70)#1  0x00000000017d3634 in ha_myisam::multi_range_read_next (this=0x2aab0000ca40, range_info=0x2aaafc03de70)#2  0x0000000000d138cc in QUICK_RANGE_SELECT::get_next (this=0x2aab00014070)#3  0x0000000000d46908 in rr_quick (info=0x2aab0000f648)#4  0x00000000009a2791 in join_init_read_record (tab=0x2aab0000f5b8)#5  0x000000000099d6d4 in sub_select (join=0x2aab000064b0, join_tab=0x2aab0000f5b8, end_of_records=false)#6  0x000000000099c914 in do_select (join=0x2aab000064b0)

二缓索引(h2)& 主建索引(h) 的协同是通过rowids_buf_cur来进行的。最初的初始化过程中,h2 会首先将数据填冲到 rowids_buf 中,如果发现缓冲区中的数据已经取完,则会继续调用 dsmrr_fill_buffer 往 rowids_buf 填主键并进行排序,如此反复,直至 h2 扫描至文件末尾,详情可以参考函数 DsMrr_impl::dsmrr_next。

通过上面的分析,是不是感觉 MRR 有点像二级索引与主键的 join 操作,那就是有点和 BKA 有些类似的概念了,咱们下面看一下 BKA 是如何实现的。

MRR 使用场景

场景A:对于InnoDB和MyISAM表的索引范围扫描和等值连接操作,可以使用MRR优化。

执行流程:

  • 索引的一部分元组被累积在缓冲区中。
  • 缓冲区中的元组按其数据行 ID 进行排序。
  • 根据排序后的索引元组序列访问数据行。

场景举例:

1)索引范围扫描:假设有一个名为orders的表,其中包含order_id和order_date列,并且为order_date列创建了一个索引。当执行以下查询时:

SELECT order_id FROM orders WHERE order_date BETWEEN '2022-01-01' AND '2022-12-31';

MRR优化可以使用索引的范围扫描,将满足条件的索引元组收集到缓冲区中,并按照数据行ID进行排序。然后,根据排序后的索引元组顺序访问数据行,而无需回表操作。

2)等值连接:假设有两个表orders和customers,它们之间通过customer_id列进行连接。当执行以下查询时:

SELECT order_id FROM orders INNER JOIN customers ON orders.customer_id = customers.customer_id;

MRR优化可以使用等值连接操作,将满足条件的索引元组收集到缓冲区中,并按照数据行ID进行排序。然后,根据排序后的索引元组顺序访问数据行,以执行等值连接操作。

场景B:对于NDB表的多范围索引扫描或按属性进行等值连接时,可以使用MRR优化。

执行流程:

  • 一部分范围(可能是单键范围)在提交查询的中央节点上被累积在缓冲区中。
  • 范围被发送到访问数据行的执行节点。
  • 访问的行被打包成数据包并发送回中央节点。
  • 接收到的带有数据行的数据包被放置在缓冲区中。
  • 数据行从缓冲区中读取。

场景举例:

1)多范围索引扫描:假设有一个NDB表products,其中包含product_id和price列,并且为price列创建了一个多范围索引。当执行以下查询时:

SELECT product_id FROM products WHERE price BETWEEN 10 AND 100;

MRR优化可以在查询提交的中央节点上,将满足条件的一部分范围(可能是单键范围)累积到缓冲区中。然后,将这些范围发送到访问数据行的执行节点。执行节点将访问的行打包并发送回中央节点。中央节点接收到包含数据行的包后,将其放入缓冲区。然后,可以从缓冲区中读取数据行。

2)按属性进行等值连接:假设有两个NDB表

orders和customers,它们之间通过customer_id列进行连接。当执行以下查询时:

SELECT order_id FROM orders INNER JOIN customers ON orders.customer_id = customers.customer_id;

MRR优化可以使用等值连接操作,将满足条件的一部分范围(可能是单键范围)累积到中央节点的缓冲区中。然后,这些范围被发送到访问数据行的执行节点。执行节点将访问的行打包并发送回中央节点。中央节点接收到包含数据行的包后,将其放入缓冲区。然后,可以从缓冲区中读取数据行。

MRR 如何使用

//如果你不打开,是一定不会用到 MRR 的
set optimizer_switch='mrr=on';
set optimizer_switch ='mrr_cost_based=off';
set read_rnd_buffer_size = 32 * 1024 * 1024;

mrr_cost_based:on/off,是用来告诉优化器,要不要基于使用 MRR 的成本,考虑使用 MRR 是否值得(cost-based choice),来决定具体的 sql 语句里要不要使用 MRR。

很明显,对于只返回一行数据的查询,是没有必要 MRR 的,而如果你把 mrr_cost_based 设为 off,那优化器就会通通使用 MRR,这在有些情况下是很 stupid 的,所以建议这个配置还是设为 on,毕竟优化器在绝大多数情况下都是正确的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/169983.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Docker 部署 Nacos(单机),利用 MySQL 数据库存储配置信息

前面的话 默认你已经懂 Docker、docker-compose Nacos版本&#xff1a;v2.2.3 MySQL 版本&#xff1a;8.2.0 一、下载 打开 Nacos 官网 官网地址&#xff1a;官网 点击手册 左侧 Nacos Docker 克隆项目到本地 # 克隆项目&#xff0c;如果提示连接不到 github 请自行解决 …

element-plus 一键动态换任何皮肤功能

plus 官方代码给的思路 如果您想要通过 js 控制 css 变量&#xff0c;可以这样做&#xff1a; // document.documentElement 是全局变量时 const el document.documentElement // const el document.getElementById(xxx)// 获取 css 变量 getComputedStyle(el).getPropertyV…

【Flutter】设置顶部状态栏的显示、隐藏、半透明灰色显示

【Flutter】设置顶部状态栏的显示、隐藏、半透明灰色显示 设置方法&#xff1a; // 这种模式不现实状态栏 SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky); // 这种模式显示状态栏 SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); // 修…

运行软件报错找不到vcruntime140_1.dll无法继续执行代码如何解决?-常见问题

关于vcruntime140_1.dll丢失的6个解决方法。在我们使用电脑的过程中&#xff0c;有时候会遇到一些错误提示&#xff0c;其中之一就是“vcruntime140_1.dll丢失”。那么&#xff0c;究竟什么是vcruntime140_1.dll文件呢&#xff1f;又是什么原因导致了它的丢失&#xff1f;接下来…

跟我学C++中级篇——STL的并行算法

一、并行算法 在STL的算法中&#xff0c;对于大多数程序员的应用&#xff0c;都是普通的单线程的库。同时&#xff0c;很多开发者也都注意到&#xff0c;在STL的库中很多都是非多线程安全的。而且随着硬件和软件技术的不段的发展&#xff0c;许多库面临着在多核和多线程环境下…

基于WSL2+Docker+VScode搭建机器学习(深度学习)开发环境

基于WSL2DockerVScode搭建机器学习(深度学习)开发环境 内容概述&#xff1a;由于最近配发了新的工作电脑但不想装双系统&#xff0c;因此通过本博文来记录基于Windows子系统WSLDocker搭建机器学习与深度学习开发环境的流程步骤&#xff0c;同时记录该过程中所遇到的相关问题及解…

交流充电桩与直流充电桩的区别

1、背景 直流充电桩的学名是非车载充电机&#xff0c;是相对于交流充电桩而言的。交流充电桩是采用传导方式为具备车载充电机的电动汽车提供交流电能的专用装置。 2、交流充电桩和直流充电桩 1.1、交流充电桩 交流充电桩包括单相和三相交流充电桩。 图一是交流充电桩原理框…

使用数据集对SegFormer模型进行微调以改进自动驾驶车辆的车道检测-附源码下载

SegFormer:细分严重影响了高级驾驶辅助系统的开发。它在自动驾驶汽车技术的快速发展中发挥了关键作用。它由多个复杂的组件组成。对于任何在道路上行驶的车辆来说,车道检测至关重要。车道是道路上的标记,有助于区分道路上的可行驶区域和不可行驶区域。当前一代有多种车道检测…

JMeter 设置请求头信息的详细步骤

在使用 JMeter 的过程中&#xff0c;我们会遇到需要设置请求头信息的场景。比如&#xff1a; POST 传过去的 Body 数据是 json 格式的。需要填添加头信息&#xff1a;Content-Type&#xff1a;application/json。 在 header 中用 token 来传用户的认证信息。 下面&#xff0c;…

5.1 Windows驱动开发:判断驱动加载状态

在驱动开发中我们有时需要得到驱动自身是否被加载成功的状态&#xff0c;这个功能看似没啥用实际上在某些特殊场景中还是需要的&#xff0c;如下代码实现了判断当前驱动是否加载成功&#xff0c;如果加载成功, 则输出该驱动的详细路径信息。 该功能实现的核心函数是NtQuerySys…

位图和布隆过滤器

目录 一. 位图 1.题目&#xff1a; 给40亿个不重复的无符号整数&#xff0c;没排过序。给一个无符号整数&#xff0c;如何快速判断一个数是否在这40亿个数中&#xff1f; 2.解析题目&#xff1a; 3.位图 4.代码以及测试 5.其他题目 二.布隆过滤器 1.介绍 2.实现 …

异步组件与函数式组件

在异步组件中&#xff0c;“异步”二字指的是&#xff0c;以异步的方式加载并渲染一个组件。这在代码分割、服务端下发组件等场景中尤为重要。而函数式组件允许使用一个普通函数定义组件&#xff0c;并使用该函数的返回值作为组件要渲染的内容。函数式组件的特点是&#xff1a;…

Java如何使用jwt进行登录拦截和权限认证

登录如下 登录拦截 拦截如下 权限认证

electron 设置开机自启动后 托盘图标无法显示

问题描述 electron 设置开机自启动后 托盘图标无法显示 问题解决 tray new Tray(path.join(__dirname, ./public/logo.png)); //必须是绝对路径和扩展名&#xff0c;像.png等我的问题是图标之前设置为相对路径&#xff0c;而导致无法显示。将Tray的图标路径设定为绝对路径后…

【Docker】从零开始:12.容器数据卷

【Docker】从零开始&#xff1a;12.容器数据卷 1.什么是容器数据库卷2.数据的覆盖问题3.为什么要用数据卷4.Docker提供了两种卷&#xff1a;5.两种卷的区别6.bind mount7.Docker managed volumevolume 语法volume 操作参数 1.什么是容器数据库卷 卷 就是目录或文件&#xff0c…

数据结构之栈与队列习题详解解析

个人主页&#xff1a;点我进入主页 专栏分类&#xff1a;C语言初阶 C语言程序设计————KTV C语言小游戏 C语言进阶 C语言刷题 数据结构初阶 欢迎大家点赞&#xff0c;评论&#xff0c;收藏。 一起努力&#xff0c;一起奔赴大厂。 目录 1.前言 2.概念题…

python_selenium自动化测试框架

&#x1f4e2;专注于分享软件测试干货内容&#xff0c;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01;&#x1f4e2;交流讨论&#xff1a;欢迎加入我们一起学习&#xff01;&#x1f4e2;资源分享&#xff1a;耗时200小时精选的「软件测试」资…

springboot程序启动成功后执行的方法

//实现该接口&#xff0c;run方法既程序启动成功后将要执行的方法 // // Source code recreated from a .class file by IntelliJ IDEA // (powered by FernFlower decompiler) //package org.springframework.boot;FunctionalInterface public interface CommandLineRunner {…

nginx的n种用法(nginx安装+正向代理+反向代理+透明代理+负载均衡+静态服务器)

nginx的安装 一、安装依赖 # 一键安装四个依赖 yum -y install gcc zlib zlib-devel pcre-devel openssl openssl-devel二、安装nginx yum install nginx三、检查是否安装成功 nginx -v四、启动/停止nginx /etc/init.d/nginx start /etc/init.d/nginx stop五、编辑配置文件…

重生之我是一名程序员 41 ——字符串函数(2)

哈喽啊大家晚上好&#xff01;今天呢我们延续昨天的内容&#xff0c;给大家带来第二个字符串函数——strcat函数。 首先呢&#xff0c;还是先带大家认识一下它。strcat函数是C语言中用于将两个字符串连接起来的函数&#xff0c;其函数原型为&#xff1a; char *strcat(char *…