当前位置: 首页 > news >正文

聊聊MySQL中的buffer pool

什么是buffer pool

       为了缓存磁盘中的页,设计InnoDB的大叔在MySQL服务器启动时就向操作系统申请了一片连续的内存,他们给这片内存起了个名字--Buffer Pool(缓冲池)。它有多大呢?这个其实取决于我们机器的配置:如果你是“土豪”,有512GB内存,分配个几百GB作为BufePool当然没有问题;如果没那么有钱,设置得小一点也问题不大。默认情况下,Bufer Pool只有128MB。如果嫌弃这个128MB太大或者太小,可以在启动服务器的时候配置imnodbbufer poolsize启动选项(这个启动选项表示 Buffer Pool的大小)的值,就像下面这样:

 [server]
innodb buffer pool size=268435456

       innodb bufer pool size 的单位是字节,所以上面的配置指定了 Buffer Pool 的大小为 256MB.需要注意的是,Bufer Pool也不能太小,最小值为5MB(当innodb_buffer_pool_size 的值小于5M 时会自动设置成5MB)。

      Buffer Pool对应的一片连续的内存被划分为若干个页面,页面大小与 ImnnoDB 表空间使用的页面大小一致,默认都是16KB。为了与磁盘中的页面区分开来,我们这里把这些Buffer Pool使用中的页面称为缓冲页。为了更好地管理Buffer Pool中的这些缓冲页,设计!0DB的大战的每一个缓冲页都创建了一些控制信息。这些控制信息包括该页所属的表空间编号、页号、为贡在 Buffer Pool中的地址、链表节点信息等。

       大家想想看,每一个控制块都对应一个缓冲页,那么在分配足够多的控制块和缓冲页后,剩余的那点儿空间可能不够一对控制块和缓冲页的大小,自然也就用不到了。这个用不到的内存空间就称为碎片。当然,如果把Bufer Pool的大小设置得刚刚好,也可能不会产生碎片。

怎么管理
        free链表

      当我们最初启动 MySOL服务器的时候,需要完成Buffer Pool的初始化过程。就是先向操作系统申请 Buffer Pool的内存空间,然后把它划分成若干对控制块和缓冲页。但是此时并没有真实的磁盘页被缓存到 Bufer Pool中(因为还没有用到),之后随着程序的运行,会不断地有磁盘上的页被缓存到 Buffer Pool中。那么问题来了,从磁盘上读取一个页到Bufer Pool中时,该放到哪个缓冲页的位置呢?或者说怎么区分 Bufer Pool中哪些缓冲页是空闲的,哪些已经被使用了呢?我们最好在某个地方记录 Bufer Pool中哪些缓冲页是可用的。这个时候缓冲页对应的控制块就派上大用场了--我们可以把所有空闲的缓冲页对应的控制块作为一个节点放到一个链表中,这个链表也free链表(或者说空闲链表)。刚刚完成初始化的 Bufer Pool中,所有的缓冲页都是空闲的,所以每一个缓冲贡对应的控制块都会加入到ee链表中。假设该Buner Pol中可容纳的缓冲页数量为n,那么增加了 free 链表的效果图如图所示。

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

     我们需要访问某个页中的数据时,就会把该页从磁盘加载到Buffer Pool如果该页已经在Buffer Pool中的话,直接使用就可以了。那么问题也就来了,我们怎么知道页在不在 Buffer Pool中呢?进不成需要依次遍历Buffer Pool中的各个缓冲页么?一个Buffer Pool中的缓冲页这么多,都遍历完岂不是要累死?

         我们其实是根据表空间号+页号来定位一个页的,也就相当于表空间号+页号是一个key缓冲页控制块就是对应的value值

        flush 链表

       如果我们修改了 Bufter Pool 中某个缓冲页的数据,它就与磁盘上的页不一致了,这样等冲页也称为脏页(dirty page)。当然,我们可以每当修改完某个缓冲页时,就立即将其刷新到磁盘中对应的页上。但是频繁地往磁盘中写数据会严重影响程序的性能(毕竟刷盘慢得“像乌龟一样”)。所以每次修改缓冲页后,我们并不着急立即把修改刷新到磁盘上,而是在未来其个时间点进行刷新。至于这个刷新的时间点会在后面进行说明,现在先不用管。但是,如果不立即将修改刷新到磁盘,那之后再刷新的时候我们怎么知道 BuferPool中哪些页是脏页,哪些页从来没被修改过呢?总不能把所有的缓冲页都刷新到磁盘上吧。假如Bufer Pool被设置得很大,比如有 300GB,那么一次性刷新这么多数据岂不是要慢死!所以我们不得不再创建一个存储脏页的链表,凡是被修改过的缓冲页对应的控制块都会作为一个节点加入到这个链表中。因为这个链表节点对应的缓冲页都是需要被刷新到磁盘上的,所以也称为flush 链表。flush 链表的构造与 free 链表差不多。

        lru链表

       Buffer Pool对应的内存大小毕竟是有限的,如果需要缓存的页占用的内存大小超过了Buffer Pool的大小,也就是链表中已经没有多余的空闲缓冲页了,这岂不是很尴尬,发生这种事该怎么办?当然是把某些旧的缓冲页从Buer Pool中移除,然后再把新的添加进来。那么问题来了移除哪些缓冲页呢?为了回答这个问题,我们还需要回到设计BuerPool的初衷--想减少磁盘IO次最好每次在访问某个页的时候它已经被加载到BuferPool中了。假设我们一共访问了n次那么被访问的页已经在Buffer Pool中的次数中除以n就是Buffer Poo的l命中率。我们的期望是Buffer Pool的命中率越高越好从这个角度出发,回想一下我们的微信聊天列表,排在前面的都是最近频繁使用的,排在后面的自然就是最近很少使用的。假如列表能容纳的联系人有限是把最近很频繁使用的留下,还是把最近很少使用的留下呢?当然是留下最近很频繁的了。

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

       如果该页不在 Buffer Pool中,在把该页从磁盘加载到 Buffer Pool中的缓冲页时,就把该缓冲页对应的控制块作为节点塞到LRU链表的头部;

        如果该页已经被加载到Buffer Pool中,则直接把该页对应的控制块移动到LRU链表
的头部。

        也就是说,只要我们使用到某个缓冲页,就把该缓冲页调整到LRU链表的头部,这LRU链表星部就是最近最少使用的缓冲页了。所以,当Bufer Pool中的空闲缓冲页使用到LRU链表的尾部找些缓冲页淘汰掉就OK了。

划分区域的 LRU 链表

     上面这个简单的LRU链表用了没多长时间就发现问题了。它存在下面这两种比较尴尬的情况

情况1:InnoDB 提供了一个看起来比较贴心的服务--预读(rcad ahead)。我i说过只有当我们用到某个页时,才会将其从磁盘加载到Buffer Pool中,用不到不加载。预读本来是个好事儿,如果预读到Bufer Pool中的页被成功地使用到,那就可以极大地提高语句执行的效率。可是如果用不到呢?这些预读的页都会放到LRU链表的头部。但是果此时 Buffer Pool的容量不太大,而且很多预读的页面都没有用到的话,就会导致处子IRU链表尾部的一些缓冲页会很快被淘法掉,从而大太降低Bufer pool命中率。

        一言蔽之,可能降低 Bufer Pool命中率的两种情况如下所示:

加载到 Buffer Pool 中的页不一定被用到

如果有非常多的使用频率偏低的页被同时加载到Bufer Pool中,则可能会把那些使用频率高的淘汰因为这两种情况的存在,设计InnoDB 的大叔把这个LRU 链表按照一定比例分成两截:

一部分存储使用颊率非常高的缓冲页;这一部分链表称为热数据

另一部分存储使用频率不是很高的缓冲页这一部分链表也称为冷数据。

        需要特别注意的一点是,我们是按照某个比例将LRU 链表分成两半的,而不是某些节点。固定位于young 区域,某些节点固定位于 old区域。随着程序的运行,某个节点所属的区可能发生变化。那么,这个划分成两截的比例是怎么确定的呢?对于InnoDB 存储引擎来港我们可以通过查看系统变量 innodb_old_blocks_pct 的值来确定 old 区域在 LRU 链表中所占比例。

        更进一步优化

对于young区域的缓存页来说,我们每次访问一个缓存页就要把它移动到头部,这样开销太大了,毕竟young区域的都是热点数据,也就是可能会经常访问,这样频繁对链表执行移动节点操作是不是不太好?为了解决这个问题,我们进一步提出一些优化策略,比如只有被访问的缓存页位与young区域1/4的后面时,才会被移动到链表头部。这样就可以降低移动节点的频率,从而提升性能

http://www.xdnf.cn/news/1124803.html

相关文章:

  • 分布式通信框架 - JGroups
  • 深度强化学习 | 图文详细推导深度确定性策略梯度DDPG算法
  • [数据结构]#3 循环链表/双向链表
  • 为什么市场上电池供电的LoRa DTU比较少?
  • FBRT-YOLO: Faster and Better for Real-Time Aerial Image Detection论文精读(逐段解析)
  • 【HarmonyOS】元服务概念详解
  • 16.避免使用裸 except
  • ELK部署与使用详解
  • L1与L2正则化详解:原理、API使用与实践指南
  • Windows下安装nvm管理多个版本的node.js
  • LVS集群技术
  • 网络--OSPF实验
  • 分布式一致性协议
  • 卷积模型的优化--Dropout、批标准化与学习率衰减
  • 每天一个前端小知识 Day 31 - 前端国际化(i18n)与本地化(l10n)实战方案
  • 分支战略论:Git版本森林中的生存法则
  • PHP password_get_info() 函数
  • 时序预测 | Pytorch实现CNN-LSTM-KAN电力负荷时间序列预测模型
  • 深入理解MyBatis延迟加载:原理、配置与实战优化
  • 设备发出、接收数据帧的工作机制
  • B站自动回复工具(破解)
  • Linux连接跟踪Conntrack:原理、应用与内核实现
  • JAVA进阶--JVM
  • 【Linux网络】:HTTP(应用层协议)
  • rk3588平台USB 3.0 -OAK深度相机适配方法
  • 网络编程(TCP连接)
  • 前端同学,你能不能别再往后端传一个巨大的JSON了?
  • 7.14练习案例总结
  • UE5多人MOBA+GAS 22、创建技能图标UI,实现显示蓝耗,冷却,以及数字显示的倒数计时还有雷达显示的倒数计时
  • C语言:20250714笔记