《MySQL实战45讲》——学习笔记33 MySQL Server查询结果的发送流程 / 一次查询大量数据对innoDB bufferPool的影响 / 内存淘汰算法LRU与innoDB改进的LRU

2023-11-02

本篇通过"大查询会不会把内存用光"这个问题,介绍了MySQL 的查询结果发送给客户端的过程,涉及的知识点包括:MySQL Server查询结果的发送流程(边读边发)、MySQL线程状态Sending to client&Sending data、一次查询大量数据对innoDB bufferPool的影响、内存淘汰算法LRU与innoDB改进的LRU;

问题:一次大查询的是否会"耗尽"数据库主机的内存?

例如:我的主机内存只有 100G,现在要对一个 200G 的大表做全表扫描,会不会把数据库主机的内存用光了?

——答案是不会!比如说逻辑备份的时候,可不就是做整库扫描吗?所以说,对大表做全表扫描,看来应该是没问题的;但是,这个流程到底是怎么样的呢?

大体的思路可能已经有了,就是分批处理的思想,多次查询加载到内存再发送给MySQL Client,下面看看Server层具体是怎么处理的;

MySQL Server查询结果的发送流程

假设,我们现在要对一个 200G 的 InnoDB 表,执行一个全表扫描,把扫描结果保存在客户端,会使用类似这样的命令:

mysql -h$host -P$port -u$user -p$pwd -e "select * from db1.t" > $target_file

InnoDB 的数据是保存在主键索引上的,所以全表扫描实际上是直接扫描表 t 的主键索引;这条查询语句由于没有其他的判断条件,所以查到的每一行都可以直接放到"结果集"里面,然后返回给客户端;那么,这个"结果集"存在哪里呢?

——实际上,服务端并不需要保存一个完整的结果集;取数据和发数据的流程是这样的:

1. 获取一行,写到 net_buffer 中;重复获取行,直到 net_buffer 写满,调用网络接口发出去;
2. 如果发送成功,就清空 net_buffer,然后继续取下一行,并写入 net_buffer;
3. 如果发送函数返回 EAGAIN 或 WSAEWOULDBLOCK,就表示本地网络栈(socket send buffer)写满了,进入等待;直到网络栈重新可写,再继续发送;

从这个流程中,你可以看到:

(1)MySQL的查询结果是分批次发出的,一个批次的大小是net_buffer的大小(默认16K),并非一次查完所有结果,全部打包一次通过发送,而是分批次的边查边发送的;

(2)socket send buffer是否写满也会影响MySQL结果的返回;这意味着,如果客户端接收得慢,会导致 MySQL Server由于结果发不出去进入发送阻塞,导致这个事务的执行时间变长

也就是说,MySQL 是“边读边发的”,这个概念很重要!这就意味着,如果客户端接收得慢,会导致 MySQL 服务端由于结果发不出去,这个事务的执行时间变长

MySQL线程状态Sending to client&Sending data

Sending to client

模拟一种情况:故意让客户端不去读 socket receive buffer 中的内容,然后在服务端 show processlist 查看结果;

可以看到,State 的值一直处于“Sending to client”,表示服务器端的网络栈写满了;假设有一个业务的逻辑比较复杂,每读一行数据以后要处理的逻辑如果很慢,就会导致客户端要过很久才会去取下一行数据,可能就会出现上面这种情况

因此,对于正常的线上业务来说,如果一个查询的返回结果不会很多的话,都建议你使用 mysql_store_result 这个接口(也是MySQL 客户端默认采用的方式),直接把查询结果保存到本地内存;但是如果非常规业务一次查询数据量非常大,如执行了一个大查询导致客户端占用内存近 20G,客户端本地缓存一次存不下,这种情况下就需要改用 mysql_use_result 接口了

Sending data

与“Sending to client”长相很类似的一个状态是“Sending data”;实际上,它跟MyAQL客户端接收数据没有太大关系;

实际上,一个查询语句的状态变化是这样的:

  1. MySQL 查询语句进入执行阶段后,首先把状态设置成“Sending data”;
  2. 然后,发送执行结果的列相关的信息(meta data) 给客户端;
  3. 再继续执行语句的流程;
  4. 执行完成后,把状态设置成空字符串;

也就是说,“Sending data”并不一定是指“正在发送数据”,而可能是处于执行器过程中的任意阶段;比如,你可以构造一个锁等待的场景,就能看到 Sending data 状态;

也就是说,仅当一个线程处于“等待客户端接收结果”的状态,才会显示"Sending to client";而如果显示成“Sending data”,它的意思只是“正在执行”;

从上面的分析可知,对于Server层,查询的结果是分批查询出来发给客户端的;因此扫描全表,查询返回大量的数据,并不会把内存打爆

Buffer Pool 加速查询

内存的数据页是在BufferPool中管理的,在WAL里BufferPool起到了加速更新的作用(先写redolog而非随机落盘,减小磁盘IO随机写压力);而实际上,BufferPool还有一个更重要的作用,就是加速查询

可能存在疑问——WAL之后如果读数据,是不是一定要读盘,是不是一定要从redolog里面把数据更新以后才可以返回?其实是不用的,此时内存里就是最新的数据(前提是内存的数据还在),磁盘上还是之前的数据,查询直接返回内存中的"脏页"即可

Buffer Pool 对查询的加速效果,依赖于一个重要的指标:内存命中率

可以在 show engine innodb status 结果中,查看一个系统当前的 BP 命中率;一般情况下,一个稳定服务的线上系统,要保证响应时间符合要求的话,内存命中率要在 99% 以上;

 

比如图中这个命中率,就是 99.1%;

如果所有查询需要的数据页都能够直接从内存得到,那是最好的,对应的命中率就是 100%;但这在实际生产上是很难做到的,因为内存大小一般是小于总数据大小的;如果一个 Buffer Pool 满了,而又要从磁盘读入一个数据页,那肯定是要淘汰一个旧数据页的

补充:redo log和change buffer

redo log和change buffer这两个概念确实容易混淆,这里再补充下change buffer的作用时机:change buffer机制不是一直会被应用到,仅当待操作的数据页当前不在内存中,需要先读磁盘加载数据页时,change buffer才有用武之地,它的目的在于避免更新前需要先将数据从磁盘读到内存中,从而减小随机IO读压力;当真的需要读这块数据时,会触发IO读并完成一次内存中的merge,产生脏页,并将更新记录到redo log(注意这里无需立即刷盘,而是有定时任务负责刷盘);细节可以参考我之前的文章:《MySQL实战45讲》——学习笔记09 “普通索引和唯一索引、change buffer 和 redo log“的最后一节;

内存淘汰算法LRU与innoDB改进的LRU

InnoDB 内存管理用的是最近最少使用 (Least Recently Used, LRU) 算法,这个算法的核心就是淘汰最久未使用的数据;

经典LRU算法模型

下图是一个传统LRU算法的基本模型;

(1)访问数据页P3,P3存在于缓存中,因此将P3被移到链表最前面;
(2)访问数据页Px,Px不存在于缓存中,因此需要在 Buffer Pool 中新申请一个数据页空间放Px,但是由于内存已经满了,所以需要清空链表末尾Pm这个数据页的内存,并存入Px的内容,然后移动到链表头部位置;

从效果上看,就是最久没有被访问的数据页Pm,被淘汰了

全表扫描下InnoDB对LRU的改进

这个算法乍一看上去没什么问题,但是如果考虑到要做一个全表扫描,会不会有问题呢?——会,内存里面的数据一直在更新,整体内存命中率变低

例如,按照这个算法,我们要扫描一个200G的表,按照这个算法扫描的话,就会把当前的BufferPool里的数据全部淘汰掉,存入扫描过程中访问到的数据页的内容,尽管这些内容被访问一次后就很快因内存空间不足被LRU淘汰了

这对于一个正在做业务服务的库的影响是很大的;你会看到,BufferPool的内存命中率急剧下降,磁盘压力增加,SQL语句响应变慢;

所以,InnoDB不能直接使用这个LRU算法;实际上,InnoDB对LRU算法做了改进;

在InnoDB实现上,按照5:3的比例把整个LRU链表分成了young区域和old区域

(1)对于young区,访问内存中已存在的Page时,更新规则和传统的LRU算法一样,放到链表头部;
(2)访问一个新的不存在于当前链表的数据页,这时候依然是淘汰掉链表尾部的数据页Pm,但是新插入的数据页Px,是放在LRU_old处;
(3)处于old区域的数据页,每次被访问的时候都要做下面这个判断:

a. 若这个数据页在LRU链表中存在的时间超过了1000ms,就把它移动到链表头部;
b. 如果这个数据页在LRU链表中存在的时间短于1000ms,则位置保持不变;

1000ms这个时间,是由参数innodb_old_blocks_time控制的;其默认值是1000,单位毫秒;

这个策略看起来就是为了处理类似全表扫描的操作量身定制的;

还是以刚刚的扫描200G的历史数据表为例,看看改进后的LRU算法的操作逻辑:

  1. 扫描过程中,需要新插入的数据页,首先会被放到old区域;
  2. 一个数据页里面有多条记录,因此这个数据页会被多次访问到;但由于是顺序扫描,在同个Page上扫描数据的速度很快,这个数据页第一次被访问和最后一次被访问的时间间隔不会超过1000ms,因此这个数据页还是会被保留在old区域;
  3. 再继续扫描后续的数据,之前的这个数据页之后也不会再被访问到,于是始终没有机会移到链表头部(也就是young区域),很快就会因为缓存空间有限被LRU淘汰出去;

可以看到,这个策略最大的改进就是重新定义了"最新使用"的含义除了传统的访问次数的特点以外,还要求被访问的时间要稍微长一点,防止这种类似全表扫描吧整个缓存刷掉的情况

改进算法的最大的收益,就是在扫描这个大表的过程中,虽然也用到了BufferPool,但是对young区域完全没有影响,从而保证了BufferPool响应正常业务的查询命中率;

下篇文章:待定

本章参考:33 | 我查这么多数据,会不会把数据库内存打爆?

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

《MySQL实战45讲》——学习笔记33 MySQL Server查询结果的发送流程 / 一次查询大量数据对innoDB bufferPool的影响 / 内存淘汰算法LRU与innoDB改进的LRU 的相关文章

随机推荐

  • chatgpt综述和报告

    ChatGPT究竟强在哪 复旦大学邱锡鹏教授 大型语言模型的能力分析与应用 哔哩哔哩 bilibili2022年底 美国OpenA1公司发布了ChatGPT 一个可以与人类对话交互的千亿规模参数的大型语言模型 它可以根据用户输入的指令完成各
  • Cocos Creator 华容道

    环境 cocos creator 2 0 10 Mac环境 TypeScript 文末附源码链接 本文提供了游戏玩法的逻辑 支持关卡配置 可以自由配置关卡 先看一下效果 首先 我们看一下我们要处理的问题 1 区域划分 2 角色摆放 即 关卡
  • openWRT添加模块(四)

    http blog chinaunix net uid 10429687 id 3374873 html OpenWrt是一个比较完善的嵌入式Linux开发平台 在无线路由器应用上已有100多个软件包 人们可以在其基础上增加软件包 以扩大其
  • PID怎么应用到实际控制系统

    早就想写篇PID应用的文章 可是一直以来受限于自己的知识水平和能力 未能如愿 整个暑假在做一个激光电源的控制程序 核心思想又是PID控制 看来在整个控制领域 PID 的市场可是无处不在啊 关于PID的理论分析这里不在赘述 可以在一般的资料上
  • 判断这个数能被4整除,但是不能被100整除

    需求 用户输入一个 判断这个数能被4整除 但是不能被100整除 分析 1 用户输入 2 控制台 是否能被4整除并且100整除 let num prompt 请输入一个数 num num 0 防止用户输入空字符若是空字符就为0 let re
  • kdj超卖_kdj超卖是什么意思?kdj超买超卖区别是什么

    kdj超卖是什么意思 kdj超买超卖区别是什么 对于大多数股民来说 指标应用是股市投资必不可少的操作系统 指标的主要作用就是用来作参考 辅助自己进行股票投资 今天 在这里我们所要谈论的是kdj指标 kdj超卖是什么意思 如何判断kdj超买超
  • angular4学习指南,环境搭建,基础概念解析(一)

    一 Angular是什么 Angular是由google开发维护的一个开发跨平台应用的框架 同时适应PC端和移动端 两个大版本 1 5 和4 0 4 0完全重写 1 5之前的叫angularJS 4 0叫angular 二 Angular开
  • Netty02-入门

    二 Netty 入门 1 概述 1 1 Netty 是什么 Netty is an asynchronous event driven network application framework for rapid development
  • dlna 斐讯r1怎么用_斐讯R1智能(蓝牙)音箱固件升级教程

    斐讯R1智能 蓝牙 音箱固件升级教程 2019 07 05 17 46 00 55点赞 459收藏 112评论 儿子现在突然爱上听歌写作业 难得是听歌品味居然跟我8分像 书桌空间有限 看来一圈蓝牙音箱 发现斐讯遗产R1 哈曼认证单元 但据说
  • Python 频繁请求问题: [Errno 104] Connection reset by peer

    记遇到的一个问题 Errno 104 Connection reset by peer 今天工作上有个需求 数据库有个表有将近3万条url记录 每条记录都是一个图片 我需要请求他们拿到每个图片存到本地 一开始我是这么写的 伪代码 impor
  • Java学习笔记16——抽象类

    抽象类 抽象类 什么是抽象类 抽象的关键字 抽象类的特点 抽象类的成员特点 抽象类 什么是抽象类 在Java中 一个没有方法体的方法 应该被定义为抽象方法 而类中如果有抽象方法 该类被定义为抽象类 抽象的关键字 abstract 抽象类的特
  • Spring 的基本用法之另外一种装配方式

    一 目的 了解 Spring 环境配置 掌握定义应用程序类 掌握编辑配置文件创建 bean 实例实现依赖注入 掌握编辑测试类创建 IoC 容器 通过容器获取 bean 实例 二 内容 创建一个Spring项目 完成主题为 订单信息通知 项目
  • 一些比较不错的资源网站

    阮一峰日志地址 Github上值得推荐的开源电子书
  • QT 5.15 源码windows下 msvc编译

    目录 1 下载 Qt 源代码 2 安装依赖项 3 配置命令行环境变量 4 构建 5 使用 1 下载 Qt 源代码 Index of archive qt 5 15 5 15 8 singlehttps download qt io arch
  • STM32F4XX/APM32F4XX USB OTA升级

    近期在研究USB CDC协议 使用USB Virtual Port Com功能与上位机通讯做了OTA功能 开发平台 MDK529开发硬件 APM32F411首先介绍一下程序执行逻辑 程序由两部分组成 Boot APP Flash由三部分组成
  • 485串口服务器协议,10/100M TCP/IP转1口RS-232/485/422串口服务器 - 宇泰(UTEK) - 全球领先智能通讯解决方案提供商!...

    gt gt 硬件特性 菜单配置界面 操作模式丰富 满足不同行业的应用 提供Windows 虚拟COM 驱动软件 灵活合理的UNIX 下Fixedtty 工作机制 具有1个串行端口 可以连接终端 Modem 条码机 收款机 ISDN 终端适配
  • 《网页设计基础——CSS的四种引入方式详解》

    网页设计基础 CSS的四种引入方式详解 一 行内式 规则 1 行内式是所有样式方法中最为直接的一种 它直接对HTML的标记使用style属性 然后将CSS代码直接写在其中 格式 p style color FF0000 font size
  • 用「渣男」心态去面试,爽翻!

    转载自博学谷公众号 面试是一场 销售 自己的考试 很多程序员掌握的技术 知识都非常过关 但在面试中却不能完全展现出来 屡屡发挥失常 同样是天选打工人 别人的offer 像猴子摘苞谷 应接不暇 而你的offer 像肉包子打狗 有去无回 究其根
  • python的环境变量配置

    一般会自己在安装时配置环境变量 但是也有小失误忘了打勾勾 这时我们则需要自己配置环境变量 Python的环境变量配置很简单 1 1 Windows配置环境变量 右击点击 我的电脑 点击 属性 2在弹出的界面中点击 高级系统设置 不同的win
  • 《MySQL实战45讲》——学习笔记33 MySQL Server查询结果的发送流程 / 一次查询大量数据对innoDB bufferPool的影响 / 内存淘汰算法LRU与innoDB改进的LRU

    本篇通过 大查询会不会把内存用光 这个问题 介绍了MySQL 的查询结果发送给客户端的过程 涉及的知识点包括 MySQL Server查询结果的发送流程 边读边发 MySQL线程状态Sending to client Sending dat