【MySQL进阶】浅谈InnoDB中的BufferPool

news2024/11/27 10:30:07

【MySQL进阶】浅谈InnoDB中的BufferPool

文章目录

  • 【MySQL进阶】浅谈InnoDB中的BufferPool
    • 一、前言——缓存的重要性
    • 二、InnoDB的Buffer Pool
      • 1:BufferPool 简介
      • 2:BufferPool内部组成
      • 3:free链表的管理
      • 4:flush链表的管理
      • 5:LRU链表的管理
        • 缓存不够的窘境
        • 划分区域的LRU链表
      • 6:刷新脏页到磁盘
      • 7:多个Buffer Pool实例

参考资料:《MySQL是怎么运行的:从根儿上理解MySQL》。

一、前言——缓存的重要性

对于使用 InnoDB 作为存储引擎的表来说,不管是用于存储用户数据的索引(包括聚簇索引和二级索引),还是各种系统数据,都是以 的形式存放在 表空间 中的,而所谓的 表空间 只不过是InnoDB 对文件系统上一个或几个实际文件的抽象,也就是说我们的数据说到底还是存储在磁盘上的。但是各位也都知道,磁盘的速度慢的跟乌龟一样,怎么能配得上“快如风,疾如电”的 CPU 呢?所以 InnoDB 存储引擎在处理客户端的请求时,当需要访问某个页的数据时,就会把完整的页的数据全部加载到内存中,也就是说即使我们只需要访问一个页的一条记录,那也需要先把整个页的数据加载到内存中。将整个页加载到内存中后就可以进行读写访问了,在进行完读写访问之后并不着急把该页对应的内存空间释放掉,而是将其 缓存 起来,这样将来有请求再次访问该页面时,就可以省去磁盘 IO 的开销了。

二、InnoDB的Buffer Pool

1:BufferPool 简介

设计 InnoDB 的大叔为了缓存磁盘中的页,在 MySQL 服务器启动的时候就向操作系统申请了一片连续的内存,他们给这片内存起了个名,叫做 Buffer Pool (中文名是 缓冲池 )。

2:BufferPool内部组成

Buffer Pool 中默认的缓存页大小和在磁盘上默认的页大小是一样的,都是 16KB 。为了更好的管理这些在Buffer Pool 中的缓存页,设计 InnoDB 的大叔为每一个缓存页都创建了一些所谓的 控制信息 ,这些控制信息包括该页所属的表空间编号、页号、缓存页在 Buffer Pool 中的地址、链表节点信息、一些锁信息以及 LSN 信息

每个缓存页对应的控制信息占用的内存大小是相同的,我们就把每个页对应的控制信息占用的一块内存称为一个控制块 吧,控制块和缓存页是一一对应的,它们都被存放到 Buffer Pool 中,其中控制块被存放到 Buffer Pool的前边,缓存页被存放到 Buffer Pool 后边,所以整个 Buffer Pool 对应的内存空间看起来就是这样的:

image-20221214013542194

碎片:每一个控制块都对应一个缓存页,那在分配足够多的控制块和缓存页后,可能剩余的那点儿空间不够一对控制块和缓存页的大小,自然就用不到喽,这个用不到的那点儿内存空间就被称为 碎片 了。

3:free链表的管理

当我们最初启动 MySQL 服务器的时候,需要完成对 Buffer Pool 的初始化过程,就是先向操作系统申请 BufferPool 的内存空间,然后把它划分成若干对控制块和缓存页。

但是此时并没有真实的磁盘页被缓存到 BufferPool 中(因为还没有用到),之后随着程序的运行,会不断的有磁盘上的页被缓存到 Buffer Pool 中。那么问题来了,从磁盘上读取一个页到 Buffer Pool 中的时候该放到哪个缓存页的位置呢?或者说怎么区分 BufferPool 中哪些缓存页是空闲的,哪些已经被使用了呢?

我们最好在某个地方记录一下Buffer Pool中哪些缓存页是可用的,这个时候缓存页对应的 控制块 就派上大用场了,我们可以把所有空闲的缓存页对应的控制块作为一个节点放到一个链表中,这个链表也可以被称作 free链表 (或者说空闲链表)。刚刚完成初始化的 Buffer Pool 中所有的缓存页都是空闲的,所以每一个缓存页对应的控制块都会被加入到 free链表 中,假设该 Buffer Pool 中可容纳的缓存页数量为 n ,那增加了 free链表 的效果图就是这样的:

image-20221214015825887

从图中可以看出,我们为了管理好这个 free链表 ,特意为这个链表定义了一个 基节点 ,里边儿包含着链表的头节点地址,尾节点地址,以及当前链表中节点的数量等信息。这里需要注意的是,链表的基节点占用的内存空间并不包含在为 Buffer Pool 申请的一大片连续内存空间之内,而是单独申请的一块内存空间。

有了这个 free链表 之后事儿就好办了,每当需要从磁盘中加载一个页到 Buffer Pool 中时,就从 free链表 中取一个空闲的缓存页,并且把该缓存页对应的 控制块 的信息填上(就是该页所在的表空间、页号之类的信息),然后把该缓存页对应的 free链表 节点从链表中移除,表示该缓存页已经被使用了

4:flush链表的管理

如果我们修改了 Buffer Pool 中某个缓存页的数据,那它就和磁盘上的页不一致了,这样的缓存页也被称为 脏页 (英文名: dirty page )。当然,最简单的做法就是每发生一次修改就立即同步到磁盘上对应的页上,但是频繁的往磁盘中写数据会严重的影响程序的性能。所以每次修改缓存页后,我们并不着急立即把修改同步到磁盘上,而是在未来的某个时间点进行同步。

但是如果不立即同步到磁盘的话,那之后再同步的时候我们怎么知道 Buffer Pool 中哪些页是 脏页 ,哪些页从来没被修改过呢?总不能把所有的缓存页都同步到磁盘上吧,假如 Buffer Pool 被设置的很大,比方说 300G ,那一次性同步这么多数据岂不是要慢死!

所以,我们不得不再创建一个存储脏页的链表,凡是修改过的缓存页对应的控制块都会作为一个节点加入到一个链表中,因为这个链表节点对应的缓存页都是需要被刷新到磁盘上的,所以也叫 flush链表 。链表的构造和 free链表 差不多,假设某个时间点 Buffer Pool 中的脏页数量为 n ,那么对应的 flush链表 就长这样:

image-20221214020038696

5:LRU链表的管理

缓存不够的窘境

管理 Buffer Pool 的缓存页其实也是这个道理,当 Buffer Pool 中不再有空闲的缓存页时,就需要淘汰掉部分最近很少使用的缓存页。不过,我们怎么知道哪些缓存页最近频繁使用,哪些最近很少使用呢?呵呵,神奇的链表再一次派上了用场,我们可以再创建一个链表,由于这个链表是为了 按照最近最少使用 的原则去淘汰缓存页的,所以这个链表可以被称为LRU链表。当我们需要访问某个页时,可以这样处理 LRU链表 :

  • 如果该页不在 Buffer Pool 中,在把该页从磁盘加载到 Buffer Pool 中的缓存页时,就把该缓存页对应的控制块 作为节点塞到链表的头部
  • 如果该页已经缓存在 Buffer Pool 中,则直接把该页对应的 控制块 移动到 LRU链表 的头部

划分区域的LRU链表

上边的这个简单的 LRU链表 用了没多长时间就发现问题了,因为存在这两种比较尴尬的情况

  • InnoDB 提供了一个看起来比较贴心的服务—— 预读 (英文名: read ahead )。所谓 预读 ,就是 InnoDB 认为执行当前的请求可能之后会读取某些页面,就预先把它们加载到 Buffer Pool 中。根据触发方式的不同, 预读 又可以细分为下边两种:

    • 线性预读

      设计 InnoDB 的大叔提供了一个系统变量 innodb_read_ahead_threshold ,如果顺序访问了某个区( extent )的页面超过这个系统变量的值,就会触发一次 异步 读取下一个区中全部的页面到 BufferPool 的请求,注意 异步 读取意味着从磁盘中加载这些被预读的页面并不会影响到当前工作线程的正常执行。

    • 随机预读

      如果 Buffer Pool 中已经缓存了某个区的13个连续的页面,不论这些页面是不是顺序读取的,都会触发一次 异步 读取本区中所有其的页面到 Buffer Pool 的请求。

    预读 本来是个好事儿,如果预读到 Buffer Pool 中的页成功的被使用到,那就可以极大的提高语句执行的效率。可是如果用不到呢?这些预读的页都会放到 LRU 链表的头部,但是如果此时 Buffer Pool 的容量不太大而且很多预读的页面都没有用到的话,这就会导致处在 LRU链表 尾部的一些缓存页会很快的被淘汰掉,也就是所谓的 劣币驱逐良币 ,会大大降低缓存命中率。

  • 可能会写一些需要扫描全表的查询语句

    扫描全表意味着什么?意味着将访问到该表所在的所有页!假设这个表中记录非常多的话,那该表会占用特别多的 页 ,当需要访问这些页时,会把它们统统都加载到 Buffer Pool 中,这也就意味着吧唧一下,Buffer Pool 中的所有页都被换了一次血,其他查询语句在执行时又得执行一次从磁盘加载到 Buffer Pool的操作。而这种全表扫描的语句执行的频率也不高,每次执行都要把 Buffer Pool 中的缓存页换一次血,这严重的影响到其他查询对 Buffer Pool 的使用,从而大大降低了缓存命中率。

总结一下上边说的可能降低 Buffer Pool 的两种情况:

  • 加载到 Buffer Pool 中的页不一定被用到。
  • 如果非常多的使用频率偏低的页被同时加载到 Buffer Pool 时,可能会把那些使用频率非常高的页从Buffer Pool 中淘汰掉。

因为有这两种情况的存在,所以设计 InnoDB 的大叔把这个 LRU链表 按照一定比例分成两截,分别是:

  • 一部分存储使用频率非常高的缓存页,所以这一部分链表也叫做 热数据 ,或者称young区域
  • 另一部分存储使用频率不是很高的缓存页,所以这一部分链表也叫做 冷数据 ,或者称 old区域

image-20221214022029892

有了这个被划分成 young 和 old 区域的 LRU 链表之后,设计 InnoDB 的大叔就可以针对我们上边提到的两种可能降低缓存命中率的情况进行优化了:

  • 针对预读的页面可能不进行后续访情况的优化

    设计 InnoDB 的大叔规定,当磁盘上的某个页面在初次加载到Buffer Pool中的某个缓存页时,该缓存页对应的控制块会被放到old区域的头部。这样针对预读到 Buffer Pool 却不进行后续访问的页面就会被逐渐从old 区域逐出,而不会影响 young 区域中被使用比较频繁的缓存页。

  • 针对全表扫描时,短时间内访问大量使用频率非常低的页面情况的优化

    全表扫描有一个特点,那就是它的执行频率非常低,谁也不会没事儿老在那写全表扫描的语句玩,而且在执行全表扫描的过程中,即使某个页面中有很多条记录,也就是去多次访问这个页面所花费的时间也是非常少的。所以我们只需要规定,在对某个处在 old 区域的缓存页进行第一次访问时就在它对应的控制块中记录下来这个访问时间,如果后续的访问时间与第一次访问的时间在某个时间间隔内,那么该页面就不会被从old区域移动到young区域的头部,否则将它移动到young区域的头部。上述的这个间隔时间是由系统变量innodb_old_blocks_time 控制的,你看:

    mysql> SHOW VARIABLES LIKE 'innodb_old_blocks_time';
    

    image-20221214022328347

综上所述,正是因为将 LRU 链表划分为 youngold 区域这两个部分,又添加了 innodb_old_blocks_time 这个系统变量,才使得预读机制和全表扫描造成的缓存命中率降低的问题得到了遏制,因为用不到的预读页面以及全表扫描的页面都只会被放到 old 区域,而不影响 young 区域中的缓存页。

6:刷新脏页到磁盘

后台有专门的线程每隔一段时间负责把脏页刷新到磁盘,这样可以不影响用户线程处理正常的请求。主要有两种刷新路径:

  • 从 LRU链表 的冷数据中刷新一部分页面到磁盘。

    后台线程会定时从 LRU链表 尾部开始扫描一些页面,扫描的页面数量可以通过系统变量innodb_lru_scan_depth 来指定,如果从里边儿发现脏页,会把它们刷新到磁盘。这种刷新页面的方式被称之为 BUF_FLUSH_LRU 。

  • 从 flush链表 中刷新一部分页面到磁盘。

    后台线程也会定时从 flush链表 中刷新一部分页面到磁盘,刷新的速率取决于当时系统是不是很繁忙。这种刷新页面的方式被称之为 BUF_FLUSH_LIST 。

7:多个Buffer Pool实例

我们上边说过, Buffer Pool 本质是 InnoDB 向操作系统申请的一块连续的内存空间,在多线程环境下,访问Buffer Pool 中的各种链表都需要加锁处理啥的,在 Buffer Pool 特别大而且多线程并发访问特别高的情况下,单一的 Buffer Pool 可能会影响请求的处理速度。所以在 Buffer Pool 特别大的时候,我们可以把它们拆分成若干个小的 Buffer Pool ,每个 Buffer Pool 都称为一个 实例 ,它们都是独立的,独立的去申请内存空间,独立的管理各种链表,独立的吧啦吧啦,所以在多线程并发访问时并不会相互影响,从而提高并发处理能力。

我们可以在服务器启动的时候通过设置 innodb_buffer_pool_instances 的值来修改 Buffer Pool 实例的个数,比方说这样:

[server]
innodb_buffer_pool_instances = 2

这样就表明我们要创建2个 Buffer Pool 实例,示意图就是这样:

image-20221214022714239

那每个 Buffer Pool 实例实际占多少内存空间呢?其实使用这个公式算出来的:

innodb_buffer_pool_size / innodb_buffer_pool_instances

也就是总共的大小除以实例的个数,结果就是每个 Buffer Pool 实例占用的大小。

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

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

相关文章

PCA与PCoA

通过分析坐标轴中样本和样本间的距离可看到 2 个样本或 2 组样本间的差异性。若2个样本或2组样本之间的直线距离较近,则表示这2个样本或2组样本差异性较小;相反则表示差异性较大。因此PCA和PCoA 具有直观性(直接看两点之间的距离)和完整性(呈现所有样本)…

spring中i18n国际化处理多语言

前言 在项目中,往往用户会存在多语言的述求,比如说一个系统既有中文的用户,又有英文的用户。怎么来实现多语言呢? 首先前后端分离的项目,前端会有自己的多语言实现方案,大致效果就是,用户切换…

rocketmq源码-pull模式拉取消息、同步拉取消息

前言 上一篇博客,记录的是push模式,异步发送netty请求拉取消息的代码,这篇博客主要记录consumer发送同步netty请求,去拉取消息的逻辑,但是对于同步发送请求,需要结合LitePullConsumer来看 在Lite PullCon…

C语言基础—运算符及优先级

本章主要讲解运算符的优先级和结合顺序 知识点&#xff1a; 运算符分类&#xff0c;记忆了解13种运算符注意运算符的易错点运算符的优先级 运算符及优先级运算符分类❗ 运算符易错不能直接连续判断“< 值 <”关于判断两个浮点数是否相等逻辑表达式&#xff08;布尔型&am…

CSS:border-image

border-image属性对图像的规格和比例比较高&#xff0c;导致使用成本比较高。另外&#xff0c;常见的场景中&#xff0c;大家更倾向于扁平化而不是非拟物化&#xff0c;边框装饰通常在项目中不会出现。 border-image是由多个CSS属性缩写的&#xff0c;比如: border-image-sou…

hc32和stm32 can波特率设置

前言 笔者在调试一款新的mcu的can通信时候&#xff0c;最麻烦的是波特率设置。由于没有弄明白其计算原理&#xff0c;经常出错&#xff0c;且不同的波特率有不同的采样点的要求。浪费了不少时间。这次一次搞明白can波特率的计算公式。 can波特率计算 在ISO 11898-1-2015 标准…

音视频基础概念(2)——音频

目录 1. 基本知识 2.采样率和采样位数 3.音频编码 4. 声道数 5. 码率 6. 音频格式 日常生活中&#xff0c;音视频随处可见&#xff0c;包括视频、音频、编解码、封装容器、音视频等概念。 1. 基本知识 音频数据的承载方式最常用的是脉冲编码调制&#xff0c;即PCM。于…

JAVA面试(2022年Java常见面试问题)

1、谈谈你对Spring中IOC和AOP的理解。 答案&#xff1a; 2、谈谈Spring的bean的创建过程和生命周期。 答案&#xff1a; 3、谈一下JVM的内存分配和垃圾回收机制。 答案&#xff1a; 4、谈一下你使用比较多的设计模式和场景。 答案&#xff1a; 5、谈一些mysql的事务隔离。 …

什么是文件系统?

【推荐阅读】 一文了解Linux上TCP的几个内核参数调优 一文剖析Linux内核中内存管理 分析linux启动内核源码 文件系统是操作系统用于明确存储设备&#xff08;常见的是磁盘&#xff0c;也有基于NAND Flash的固态硬盘&#xff09;或分区上的文件的方法和数据结构&#xff0c;…

传奇列表上传登录器公告小窗口怎么修改

传奇列表上传登录器公告小窗口怎么修改 很多小伙伴不会上传列表&#xff0c;我是艾西今天给大家分享下怎么上传列表 我们开始实操&#xff08;纯教学分享&#xff09; 在我们的网站文件夹里创建一个列表.txt 在浏览器里找一个列表模板例&#xff1a;www.pkp123.cn:88&#xff…

1570_AURIX_TC275_SCU_ERU

全部学习汇总&#xff1a; GreyZhang/g_TC275: happy hacking for TC275! (github.com) 从系统的逻辑图看&#xff0c;能够很清楚看到这个模块的功能处理过程&#xff1a;首先是边沿信号的识别&#xff0c;接着是根据判断进行置位处理&#xff0c;最后进行工作触发。如果设置的…

使用Nordic的nRF52840 Dongle配合Wireshark对蓝牙设备抓包(BLE)

硬件准备&#xff1a; 1&#xff09;nRF52840 Dongle 2&#xff09;待抓包的蓝牙设备 软件准备&#xff1a; 1&#xff09;Python 2&#xff09;Wireshark 3&#xff09;nRF Sniffer for Bluetooth LE Python安装 需要注意的是下载的nRF Sniffer for Bluetooth LE版本是否…

K8s 之 Deployment 应用案例

目录一、YAML 配置文件二、运行服务三、更新 Deployment3.1 动态伸缩容3.2 触发上线四、故障自动转移五、指定节点运行 Pod六、删除 deployment一、YAML 配置文件 我们要清楚&#xff0c;在 K8s 中有两种创建资源的方式&#xff1a; &#xff08;1&#xff09;命令行方式&…

【GO】 K8s 管理系统项目[API部分--Pod]

K8s 管理系统项目[API部分–Pod] 前端: Vueelement plus 后端: gogin 1. 功能设计 2. 初始化 2.1创建项目 2.2 配置goproxy GOPROXYhttps://goproxy.cn 2.3 添加格式化工具 2.4 安装模块 go get k8s.io/client-go/tools/clientcmd go get k8s.io/api/core/v1 go get k8s.i…

使用 docker buildx 构建跨平台 Go 镜像

目录 前提 docker buildx 启用 Buildx builder 实例 构建驱动 buildx 的跨平台构建策略 一次构建多个架构 Go 镜像实践 源代码和 Dockerfile 执行跨平台构建 验证构建结果 如何交叉编译 Golang 的 CGO 项目 准备交叉编译环境和依赖 交叉编译 CGO 示例 总结 参考链接…

供水设备远程监控客户案例

一、客户介绍 客户积累多年的技术研发和工程运维经验&#xff0c;对传统的恒压供水工程所面临的维护难、维修难、运维效率低和能耗管控弱等诸多问题有深刻的体会&#xff0c;经过广泛调研&#xff0c;客户最终选择使用蓝蜂物联网的云平台和边缘计算产品对恒压供水设备和工程进行…

数据处理指令(一)—— 搬移指令MOV、MVN

数据处理指令指的是和数学运算、逻辑运算相关的指令&#xff0c;比如加减乘、与或非、赋值比较等 目录 1、MOV —— 直接搬移 (1) MOV 指令格式 (2) MOV生成指令的策略&#xff08;MOV的优点&#xff09; (3) MOV 只能搬移“立即数”的原因&#xff08;MOV的缺点&#x…

问卷设计一:问卷题目哪些有类型和注意要点?

问卷法常被人们应用于社会调查中&#xff0c;它能反馈出最真实的社会信息。所以&#xff0c;很多企业为了最大程度地了解市场&#xff0c;也经常使用问卷调查法进行研究。不过&#xff0c;想要发挥出问卷法的最大用处&#xff0c;前提是要将问卷设计规范并且可量化。 想要设计…

用ArkTs在鸿蒙系统上画一个世界杯海报

偶然看到了CSDN关于世界杯的征文活动&#xff1a; 用代码画一个足球&#xff1f; 哈哈很有意思&#xff01; 想了想&#xff0c;画一个自定义View&#xff08;足球&#xff09;&#xff0c;当然是使用Canvas了&#xff0c;但除了Canvas还有没有其它方法呢&#xff1f;那是必须…

c语言算数转换 操作符

【题目名称】下面代码的结果是&#xff1a;( b)#include <stdio.h> int main() {int a, b, c;a 5;c a;b c, c, a, a;//逗号表达式从左向右以此计算 表达式结果是最后一个表达式b a c; //a9 先算加后算加等printf("a %d b %d c %d\n:", a, b, c);retu…