Mysql的BufferPool

Mysql的BufferPool

Mysql是一个存储数据到磁盘的进程,但是磁盘的速度难以与CPU相比,所以InnoDB存储引擎在处理客户端的请求时,当需要访问某个页的数据时,就会把完整的页的数据全部加载到内存中。将整个页加载到内存中后就可以进行读写访问了,在进行完读写访问之后并不着急把该页对应的内存空间释放掉,而是将其缓存起来,这样将来有请求再次访问该页面时,就可以省去磁盘IO的开销了。
设计InnoDB的大佬为了缓存磁盘中的页,在MySQL服务器启动的时候就向操作系统申请了一片连续的内存,他们给这片内存起了个名,叫做Buffer Pool(中文名是缓冲池)
可以用如下参数设置大小:

[server]
innodb_buffer_pool_size = 268435456

其中,268435456的单位是字节,也就是我指定Buffer Pool的大小为256M。需要注意的是,Buffer Pool也不能太小,最小值为5M(当小于该值时会自动设置成5M)。

BufferPool的内部组成

Buffer Pool中默认的缓存页大小和在磁盘上默认的页大小是一样的,都是16KB。为了更好的管理这些在Buffer Pool中的缓存页,设计InnoDB的大佬为每一个缓存页都创建了一些所谓的控制信息,这些控制信息包括该页所属的表空间编号、页号、缓存页在Buffer Pool中的地址、链表节点信息、一些锁信息以及LSN信息(锁和LSN我们之后会具体介绍,现在可以先忽略),当然还有一些别的控制信息
每个缓存页对应的控制信息占用的内存大小是相同的,我们就把每个页对应的控制信息占用的一块内存称为一个控制块吧,控制块和缓存页是一一对应的,它们都被存放到 Buffer Pool 中,其中控制块被存放到 Buffer Pool 的前面,缓存页被存放到 Buffer Pool 后边,所以整个Buffer Pool对应的内存空间看起来就是这样的:
在这里插入图片描述

free链表的管理

当我们最初启动MySQL服务器的时候,需要完成对Buffer Pool的初始化过程,就是先向操作系统申请Buffer Pool的内存空间,然后把它划分成若干对控制块和缓存页。
那么问题来了,从磁盘上读取一个页到Buffer Pool中的时候该放到哪个缓存页的位置呢?或者说怎么区分Buffer Pool中哪些缓存页是空闲的,哪些已经被使用了呢?我们最好在某个地方记录一下Buffer Pool中哪些缓存页是可用的,这个时候缓存页对应的控制块就派上大用场了,我们可以把所有空闲的缓存页对应的控制块作为一个节点放到一个链表中,这个链表也可以被称作free链表(或者说空闲链表)。刚刚完成初始化的Buffer Pool中所有的缓存页都是空闲的,所以每一个缓存页对应的控制块都会被加入到free链表中,假设该Buffer Pool中可容纳的缓存页数量为n,那增加了free链表的效果图就是这样的:
在这里插入图片描述

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

缓存页的哈希处理

当我们需要访问某个页中的数据时,就会把该页从磁盘加载到Buffer Pool中,如果该页已经在Buffer Pool中的话直接使用就可以了。那么问题也就来了,我们怎么知道该页在不在Buffer Pool中呢?难不成需要依次遍历Buffer Pool中各个缓存页么?
,我们其实是根据表空间号 + 页号来定位一个页的,也就相当于表空间号 + 页号是一个key,缓存页就是对应的value,怎么通过一个key来快速找着一个value呢? 所以我们可以用表空间号 + 页号作为key,缓存页作为value创建一个哈希表,在需要访问某个页的数据时,先从哈希表中根据表空间号 + 页号看看有没有对应的缓存页,如果有,直接使用该缓存页就好,如果没有,那就从free链表中选一个空闲的缓存页,然后把磁盘中对应的页加载到该缓存页的位置。

flush链表的管理

如果我们修改了Buffer Pool中某个缓存页的数据,那它就和磁盘上的页不一致了,这样的缓存页也被称为脏页(英文名:dirty page)。
最简单的做法就是每发生一次修改就立即同步到磁盘上对应的页上,但是频繁的往磁盘中写数据会严重的影响程序的性能(毕竟磁盘慢的像乌龟一样)。所以每次修改缓存页后,我们并不着急立即把修改同步到磁盘上,而是在未来的某个时间点进行同步。
但是如果不立即同步到磁盘的话,那之后再同步的时候我们怎么知道Buffer Pool中哪些页是脏页,哪些页从来没被修改过呢?总不能把所有的缓存页都同步到磁盘上吧,假如Buffer Pool被设置的很大,比方说300G,那一次性同步这么多数据岂不是要慢死!所以,我们不得不再创建一个存储脏页的链表,凡是修改过的缓存页对应的控制块都会作为一个节点加入到一个链表中,因为这个链表节点对应的缓存页都是需要被刷新到磁盘上的,所以也叫flush链表。链表的构造和free链表差不多,

LRU链表的管理

缓存不够的窘境

如果free链表都被占满了的情况怎么办?
可以采用最近最少使用的原则去淘汰缓存页(LRU)。

  • 如果该页不在Buffer Pool中,在把该页从磁盘加载到Buffer Pool中的缓存页时,就把该缓存页对应的控制块作为节点塞到链表的头部。
  • 如果该页已经缓存在Buffer Pool中,则直接把该页对应的控制块移动到LRU链表的头部。
    也就是说:只要我们使用到某个缓存页,就把该缓存页调整到LRU链表的头部,这样LRU链表尾部就是最近最少使用的缓存页喽~ 所以当Buffer Pool中的空闲缓存页使用完时,到LRU链表的尾部找些缓存页淘汰就OK啦。

划分区域的LRU链表

下面两种情况很容易触发空间占满的情形:

  • 情况一:InnoDB提供了一个看起来比较贴心的服务——预读
  • 情况二:有的小伙伴可能会写一些需要扫描全表的查询语句(比如没有建立合适的索引或者压根儿没有WHERE子句的查询)。
    这两种情况会产生如下两种问题
  • 加载到Buffer Pool中的页不一定被用到。
  • 如果非常多的使用频率偏低的页被同时加载到Buffer Pool时,可能会把那些使用频率非常高的页从Buffer Pool中淘汰掉。
    总体而言会大大降低了缓存命中率。
    因为有这两种情况的存在,所以设计InnoDB的大佬把这个LRU链表按照一定比例分成两截,分别是:
  • 一部分存储使用频率非常高的缓存页,所以这一部分链表也叫做热数据,或者称young区域。
  • 另一部分存储使用频率不是很高的缓存页,所以这一部分链表也叫做冷数据,或者称old区域。
    们可以通过查看系统变量innodb_old_blocks_pct的值来确定old区域在LRU链表中所占的比例
SHOW VARIABLES LIKE 'innodb_old_blocks_pct';
+-----------------------+-------+
| Variable_name         | Value |
+-----------------------+-------+
| innodb_old_blocks_pct | 37    |
+-----------------------+-------+
1 row in set (0.01 sec)

从结果可以看出来,默认情况下,old区域在LRU链表中所占的比例是37%,也就是说old区域大约占LRU链表的3/8。这个比例我们是可以设置的,我们可以在启动时修改innodb_old_blocks_pct参数来控制old区域在LRU链表中所占的比例,比方说这样修改配置文件:

 [server]
innodb_old_blocks_pct = 40

或者修改

SET GLOBAL innodb_old_blocks_pct = 40;
  • 针对预读的页面可能不进行后续访情况的优化
    设计InnoDB的大佬规定,当磁盘上的某个页面在初次加载到Buffer Pool中的某个缓存页时,该缓存页对应的控制块会被放到old区域的头部。这样针对预读到Buffer Pool却不进行后续访问的页面就会被逐渐从old区域逐出,而不会影响young区域中被使用比较频繁的缓存页。
  • 针对全表扫描时,短时间内访问大量使用频率非常低的页面情况的优化
    在对某个处在old区域的缓存页进行第一次访问时就在它对应的控制块中记录下来这个访问时间,如果后续的访问时间与第一次访问的时间在某个时间间隔内,那么该页面就不会被从old区域移动到young区域的头部,否则将它移动到young区域的头部。上述的这个间隔时间是由系统变量innodb_old_blocks_time控制的,你看:
mysql> SHOW VARIABLES LIKE 'innodb_old_blocks_time';
+------------------------+-------+
| Variable_name          | Value |
+------------------------+-------+
| innodb_old_blocks_time | 1000  |
+------------------------+-------+
1 row in set (0.01 sec)

刷新脏页到磁盘

后台有专门的线程每隔一段时间负责把脏页刷新到磁盘,这样可以不影响用户线程处理正常的请求。主要有两种刷新路径:
从LRU链表的冷数据中刷新一部分页面到磁盘。
  后台线程会定时从LRU链表尾部开始扫描一些页面,扫描的页面数量可以通过系统变量innodb_lru_scan_depth来指定,如果从里边儿发现脏页,会把它们刷新到磁盘。这种刷新页面的方式被称之为BUF_FLUSH_LRU。
从flush链表中刷新一部分页面到磁盘。
后台线程也会定时从flush链表中刷新一部分页面到磁盘,刷新的速率取决于当时系统是不是很繁忙。这种刷新页面的方式被称之为BUF_FLUSH_LIST。
这里还需要看redolog来补充
https://time.geekbang.org/column/article/68633
https://relph1119.github.io/mysql-learning-notes/#/mysql/20-%E8%AF%B4%E8%BF%87%E7%9A%84%E8%AF%9D%E5%B0%B1%E4%B8%80%E5%AE%9A%E8%A6%81%E5%8A%9E%E5%88%B0-redo%E6%97%A5%E5%BF%97%EF%BC%88%E4%B8%8A%EF%BC%89

多个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

总结

  1. 磁盘太慢,用内存作为缓存很有必要。
  2. Buffer Pool本质上是InnoDB向操作系统申请的一段连续的内存空间,可以通过innodb_buffer_pool_size来调整它的大小。
  3. Buffer Pool向操作系统申请的连续内存由控制块和缓存页组成,每个控制块和缓存页都是一一对应的,在填充足够多的控制块和缓存页的组合后,Buffer Pool剩余的空间可能产生不够填充一组控制块和缓存页,这部分空间不能被使用,也被称为碎片。
  4. InnoDB使用了许多链表来管理Buffer Pool。
  5. free链表中每一个节点都代表一个空闲的缓存页,在将磁盘中的页加载到Buffer Pool时,会从free链表中寻找空闲的缓存页。
  6. 为了快速定位某个页是否被加载到Buffer Pool,使用表空间号 + 页号作为key,缓存页作为value,建立哈希表。
  7. 在Buffer Pool中被修改的页称为脏页,脏页并不是立即刷新,而是被加入到flush链表中,待之后的某个时刻同步到磁盘上。
  8. LRU链表分为young和old两个区域,可以通过innodb_old_blocks_pct来调节old区域所占的比例。首次从磁盘上加载到Buffer Pool的页会被放到old区域的头部,在innodb_old_blocks_time间隔时间内访问该页不会把它移动到young区域头部。在Buffer Pool没有可用的空闲缓存页时,会首先淘汰掉old区域的一些页。
  9. 我们可以通过指定innodb_buffer_pool_instances来控制Buffer Pool实例的个数,每个Buffer Pool实例中都有各自独立的链表,互不干扰。
  10. 自MySQL 5.7.5版本之后,可以在服务器运行过程中调整Buffer Pool大小。每个Buffer Pool实例由若干个chunk组成,每个chunk的大小可以在服务器启动时通过启动参数调整。
  11. 可以用下面的命令查看Buffer Pool的状态信息:
SHOW ENGINE INNODB STATUS\G

参考:
InnoDB的Buffer Pool

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

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

相关文章

Vue3_基础使用_1

这节主要介绍: vue2与vue3的区别,创建响应式的数据,setup语法糖的使用,watch监听,及vue3创建项目。 vue2的选项式与vue3的组合式区别: 选项式:vue2中数据与方法计算属性等等,针对…

TryHackMe-Valley

靶场介绍 Boot the box and find a way in to escalate all the way to root! 启动盒子,找到一种方法,一路升级到root! 信息收集&端口利用 使用nmap对靶机进行扫描 nmap 10.10.138.189开放22、80和37370端口,先访问80端口查…

Facebook未来展望:社交媒体的下一个篇章

社交媒体一直是连接人与人之间的纽带,而Facebook则一直在推动这一领域的发展。随着科技不断演进和社会需求的不断变迁,Facebook正积极筹谋社交媒体的下一个篇章。本文将深入剖析Facebook的未来展望,探讨其在社交媒体领域所迎接的新时代。 1. …

【开源】SpringBoot框架开发农村物流配送系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 系统登录、注册界面2.2 系统功能2.2.1 快递信息管理:2.2.2 位置信息管理:2.2.3 配送人员分配:2.2.4 路线规划:2.2.5 个人中心:2.2.6 退换快递处理:…

【c语言】strcpy()和strncpy():字符串复制详解

🎈个人主页:甜美的江 🎉欢迎 👍点赞✍评论⭐收藏 🤗收录专栏:c语言 🤝希望本文对您有所裨益,如有不足之处,欢迎在评论区提出指正,让我们共同学习、交流进步&a…

TryHackMe-File Inclusion练习

本文相关的TryHackMe实验房间链接:TryHackMe | Why Subscribe 路径遍历(目录遍历) LocationDescription/etc/issue包含要在登录提示之前打印的消息或系统标识。/etc/profile控制系统范围的默认变量,例如导出(Export)变量、文件创…

Kotlin 协程1:深入理解withContext

Kotlin 协程1:深入理解withContext 引言 在现代编程中,异步编程已经变得非常重要。在 Kotlin 中,协程提供了一种优雅和高效的方式来处理异步编程和并发。在这篇文章中,我们将深入探讨 Kotlin 协程中的一个重要函数:wi…

【JAVA】守护线程是什么?

🍎个人博客:个人主页 🏆个人专栏:JAVA ⛳️ 功不唐捐,玉汝于成 目录 正文 我的其他博客 正文 在计算机编程中,守护线程(daemon thread)是一种在程序运行时在后台提供服务的线程…

2024021期传足14场胜负前瞻

2024021期赛事由亚洲杯2场、英超5场,德甲6场、非洲杯1场组成。售止时间为2月3日(周六)19点00分,敬请留意: 本期1.5以下赔率3场,1.5-2.0赔率3场,其他场次基本皆是平半盘、平盘。本期14场整体难度…

【详细教程】Kubernetes集群部署:使用kubeadm创建集群

文章目录 一、虚拟机准备(一)主机基本配置(二)安装docker(三)配置cri-docker环境(四)安装kubeadm、kubelet、kubectl(五)克隆主机 二、环境配置工作&#xff…

LeetCode:138. 随机链表的复制之如何有效copy

自己复制的话,很容易写出来一个时间复杂度O(n ^ 2) 空O(n)的做法 我们可以参考基因的复制, 目录 题目: 实现思路(基因复制式的copy): 官方快慢指针解法&…

R语言学习case6:ggplot基础画图(Scatter散点图)

step1: 导入ggplot2库文件 library(ggplot2)step2&#xff1a;带入自带的iris数据集 iris <- datasets::irisstep3&#xff1a;查看数据信息 dim(iris)维度为 [150,5] head(iris)查看数据前6行的信息 step4&#xff1a;利用ggplot工具包绘图 plot1 <- ggplot(iris…

解决WARNING: IPv4 forwarding is disabled. Networking will not work的具体操作步骤

IPv4转发禁用警告&#xff1a;网络无法正常工作 在使用网络连接的过程中&#xff0c;我们可能遇到警告消息“WARNING: IPv4 forwarding is disabled. Networking will not work”&#xff08;警告&#xff1a;IPv4转发已禁用&#xff0c;网络将无法正常工作&#xff09;。这个…

【sentinel流量卫兵搭建与微服务整合】

sentinel流量卫兵搭建与微服务整合 搭建sentinel dashboard控制台微服务整合 搭建sentinel dashboard控制台 1、下载 官网链接 由于官网github网络原因&#xff0c;导致长时间下载失败。 网盘链接 网盘提取码&#xff1a;dwgj 2、运行 将下载jar包放在任意非中文、不包含特殊…

Mac如何设置一位数密码?

一、问题 Mac如何设置一位数密码&#xff1f; 二、解答 1、打开终端 2、清除全局账户策略 sudo pwpolicy -clearaccountpolicies 输入开机密码&#xff0c;这里是看不见的&#xff0c;输入完回车即可 3、重新设置密码 &#xff08;1&#xff09;打开设置-->用户和群组…

Next.js初识

Next.js初识 Next.js&#xff1a;这是一个用于生产环境的React 框架&#xff08;国外用的比较多&#xff09;。 Next.js 为您提供生产环境所需的所有功能以及最佳的开发体验&#xff1a;包括静态及服务器端融合渲染、 支持 TypeScript、智能化打包、 路由预取等功能 无需任何配…

Android平台如何实现RTSP转GB28181

为什么要做GB28181设备接入侧&#xff1f; 实际上&#xff0c;在做Android平台GB28181设备接入模块的时候&#xff0c;我们已经有了非常好的技术积累&#xff0c;比如RTMP推送、轻量级RTSP服务、一对一互动模块、业内几乎最好的RTMP|RTSP低延迟播放器。 Android平台GB28181接…

爱、自由与创造——教育改革的三大基石

爱、自由与创造——教育改革的三大基石 Love, Freedom, and Creativity: The Three Pillars of Educational Reform 在当今社会快速发展的背景下&#xff0c;创造性思维的重要性日益凸显。然而&#xff0c;我们必须认识到&#xff0c;创造性并非凭空产生&#xff0c;而是深深植…

MySQL | 一定会走索引却没有走的原因,日志报conversion相关错误

TL;DR&#xff1a;函数作用于表字段时&#xff0c;索引会失效。 具体情况 Indexes如下&#xff1a; SQL如下&#xff1a; explain select *from accounting_status_flow_tab_00000000WHERE ( client_no 6848134000 and loan_no 0119324345936016261000 )order by modif…

【python接口自动化】- PyMySQL数据连接

&#x1f525; 交流讨论&#xff1a;欢迎加入我们一起学习&#xff01; &#x1f525; 资源分享&#xff1a;耗时200小时精选的「软件测试」资料包 &#x1f525; 教程推荐&#xff1a;火遍全网的《软件测试》教程 &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1…