Buffer Pool详解

news2025/1/23 10:37:08

文章目录

      • 一、简介
      • 二、缓存页
      • 三、Free链表
      • 四、Flush链表
      • 五、LRU链表
      • 六、脏页刷新
      • 七、多个Buffer pool
      • 八、Chunk单位

一、简介

​ mysql的数据都是存放在磁盘下的,为了加快cpu从磁盘i/o读取数据的效率,Innodb存储引擎在cpu和磁盘中间添加了一个缓冲区buffer pool。当一个请求进来,会先从buffer pool中去看需要的查询结果数据是否已经存在,存在则直接返回,不存在,则从磁盘读取记录所在页的数据,加载到buffer pool中缓存起来。

1.buffer pool大小

​ 可以通过以下命令查看缓冲池的大小:

show variables like 'innodb_buffer_pool_size'

​ Buffer pool的默认值是128M,我们可以通过变量innodb_buffer_pool_size的大小来设置,最小不能小于5M,若是设置为5M以下,则系统会自动设置为5M。在专用数据库中,buffer pool的大小一般设置为服务器内存的60%。

二、缓存页

1.使用页为存储单位

​ mysql在磁盘中是按页为单位进行数据存储的,默认的页大小是16kb,当我们查询一条数据时,会把这条数据所属的这一页数据都加载进来;buffer pool中缓存数据也是按页为单位进行存储的,默认大小也是16kb。

2.使用控制块关联缓存页

​ 缓存页存放的是数据的具体记录,为了便于定位缓存页、记录缓存页信息,buffer pool中使用控制块来维护这些信息,控制块与缓存页是一一对应的,在mysql服务启动的时候,会完成buffer pool的初始化,申请的内存空间会被划分为若干的控制块和缓存页,此时的控制块记录着对应的缓存页地址,缓存页是空数据的状态。对应关系图如下:
在这里插入图片描述
控制块记录的信息包括:缓存页在buffer pool的地址、该页所属的表空间编号、页号、链表节点信息、锁信息。

每个控制块大约占缓存页的5%,而系统设置的innodb_buffer_pool_size的大小不包括控制块,所以Innodb存储引擎向操作系统申请的内存空间,会比设置的innodb_buffer_pool_size大5%左右。控制块排在前面,缓存页排在后面,他们之间会存在没有分配完的内存碎片。

3.使用哈希记录缓存页

​ 当存储引擎执行sql,获取到结果值所在的表空间号、页号后,需要知道结果页是否已经缓存在buffer pool中,此时不能去遍历所有的控制块看是否已经存在,这样会特别耗时;Innodb是这样处理的:维护一个哈希表,以表空间号+页号作为key,缓存页内存地址为value,在时间复杂度为O(1)的情况下即可判断结果页是否在缓存页中。若是存在,则直接使用此缓存页的数据;若是不存在,则从磁盘中加载对应的页到内存中,找一个空闲控制块,把页的表空间号、页号添加到控制块中,根据控制块的缓存页地址找到缓存页,把查询的此页数据添加到缓存页中。

4.预读机制加载缓存页

​ innodb提供了预读(read_ahead)加载缓存页的机制,就是当innodb执行一个请求查询到某个页数据后,会把之后有可能使用到的其它页数据也读取加载到buffer pool中。触发预读的方式分为两种:随机预读、线性预读。

​ 在开始说预读之前,先了解一下mysql区(extent)的概念。区(extent):区是页的集合,一个区内包含物理上连续的64个页,一个页的默认大小是16kb,一个区的默认大小是64*16kb=1mb。一个表空间划分为多个区,每256个区划分为一个组。

​ 区的结构图是这样的:
在这里插入图片描述
​ 表空间结构图是这样:
在这里插入图片描述
随机预读

​ 如果Buffer pool已经缓存了某个区(extent)连续的13个页面,不管这13个页面是顺序读取还是间隔读取进来的,会触发一次异步读取本区下的其它页加载到buffer pool的请求。innodb使用系统变量innodb_random_read_ahead来设置是否开启随机预读,默认是OFF关闭的状态,如果想要开启,可以通过修改启动参数,或者通过SET GLOBAL把该参数值设置为ON。

线性预读

​ 如果Innodb顺序访问了某个区(extent)下的页面数量超过某个值,则会触发预读机制,异步读取下一个区中全部的页面到buffer pool的请求。此值由系统变量innodb_read_ahead_threshold预读因子来控制,默认为56,此系统变量的设置范围为0-64,因为一个区最多有64个页。若是想改变此值,可以通过修改启动参数,或者通过SET GLOBAL设置该参数值。

三、Free链表

​ 当我们从磁盘读取一页的数据,需要把它加载到buffer pool中时,需要知道哪些缓存页是空闲的,哪些缓存页已经被占用了,此时不能去遍历所有的缓存页看是否空闲。innodb是这样处理的:使用一个空闲链表即free链表来存放还处于空闲状态的缓存页对应的控制块,当需要把页的数据加入到buffer pool时,从free链表中获取一个控制块,通过控制块记录的缓存页地址,找到缓存页,把查询到的页数据加入到缓存页中,把此页所属的表空间编号、页号信息写入此缓存页对应的控制块,最后从free链表中移出此控制块。

​ 当mysql启动,完成buffer pool的初始化,划分好控制块和缓存页对后,会把所有的控制块加入到free链表中,因为mysql刚启动,所有的缓存页都是空闲状态,这也是free链表的初始化。当有新的页需要加入到buffer pool,则从free链表选择一个控制块移除;当存放在buffer pool的缓存页被淘汰,则会把此缓存页对应的控制块加入到free链表中,标识此控制块对应的缓存页现在已经空闲。

​ free链表的效果图是这样:
在这里插入图片描述
​ 在free链表中定义了一个基节点,它记录了链表的start头节点地址、end尾节点地址、count=n当前链表中的节点数量。

四、Flush链表

​ 当innodb执行更新sql的时候,更新的是buffer pool中对应的缓存页数据。当更新完缓存页的数据后,此时更新的这一页数据与从磁盘中加载来时的页数据已经不一致了,也不能每次更新完缓存页的记录,立马同步更新到磁盘中的对应页去,因为磁盘的io速率是远远不及内存的。innodb是这样处理的:当修改完缓存页的数据后,不会立马把修改同步到磁盘对应页中,而是使用一张冲刷链表即flush链表记录此变动缓存页对应的控制块,在未来的某个时间点把变动页从flush链表中刷新到磁盘去,然后把此控制块从flush链表中移除。

​ flush链表的效果图是这样:
在这里插入图片描述

五、LRU链表

​ buffer pool的大小是有限的,一直往里面加入缓存页,free链表会有移除为空的时候,当free链表为空,有新的页数据需要加入进来,就需要对已经加载到buffer pool中的缓存页进行淘汰处理,系统肯定希望淘汰最不频繁使用的缓存页。innodb是这样处理的:使用LRU(Least Recently Used)最近最少使用算法来淘汰缓存页,LRU链表记录访问过的缓存页对应的控制块。

​ LRU链表的工作流程是这样的:当访问某个页时,如果此页不在buffer pool中,则从磁盘加载此页数据到缓存页中,把该缓存页对应的控制块加入到LRU链表的头部;当此页已经在buffer pool中,则把该页对应的控制块移动到LRU链表头部。

​ 这样LRU链表的尾部就是最近最少使用的控制块,当缓存页不够的时候,可以从LRU链表尾部淘汰控制块,连带淘汰控制块对应的缓存页。

1.LRU优化

​ 这样每次有访问缓存页就把对应的控制块放到LRU链表的头部,会有一些问题。比如通过预读机制加载进来的页数据、全表扫描(没有where过滤条件)加载进来的页数据,使用频率可能不高,可能会把使用频率较高的缓存页数据淘汰掉,甚至会使buffer bool中的缓存页进行一次大清洗,而使用buffer pool就是为了提高缓存的命中率,减少对磁盘io的次数。

​ Innodb是这样优化LRU的:把LRU链表按照一定比例分成两截,第一部分存储使用频率非常高的缓冲页对应的控制块,这一部分链表也叫热数据,或者young区域;第二部分存储使用频率不是很高的缓存页对应的控制块,这一部分链表也叫冷数据,或者old区域。Innodb规定,当某个页初次加载到buffer pool中时,缓存页对应的控制块加到LRU链表的old区域的头部,这样通过预读加载进来的页,放到old区域,不影响young区域的数据,后续会从old区域逐渐被淘汰;当再次访问old区域控制块对应的缓存页时,此控制块会从old区域移动到young区域的头部,此缓存页作为热数据处理;当young区域放满后,又有新的控制块需要从old区域移动到young,则young区域的链尾移动到old区域的链头;当再次访问young区域控制块对应的缓存页时,此控制块移动到young区域的头部。

​ LRU链表的效果图是这样:
在这里插入图片描述
​ young和old区域的划分比例由一个系统变量innodb_old_blocks_pct来控制,此值设置了old区域所占的百分比,默认情况下,old区域在LRU链表中所占的比例是37%,大约占LRU链表的3/8,这个比例可以通过修改启动参数,或者通过SET GLOBAL设置该参数值。可以通过此命令查看old区域占比:

show variables like 'innodb_old_blocks_pct'

2.全表扫描优化

​ 但是此种方式对于全表扫描的操作会存在问题,全表扫描的时候,一个页有多条记录需要访问,每访问一条记录就算是访问一次缓存页,则会多次访问此缓存页,还是存在把young区域大清洗的情况,针对这个问题,innodb对每次访问old区域控制块时,都在控制块中记录下当前访问时间,当再次访问时,会比较这次时间与上次访问时间间隔,若是时间间隔小于某个值,则不会把此控制块从old区域移动到young区域,此时间间隔值由系统变量innodb_old_blocks_time,默认值是1000,单位毫秒,即默认1S。这样在进行全表扫描时,多次访问一个页面的时间间隔不会超过1S,即不会把此页对应的控制块移动到young区域头部。

3.young区域优化

​ 当再次访问young区域控制块对应的缓存页时,此控制块移动到young区域的头部,这样每次访问young区域都要进行移动,开销太大了。可以使用一些优化措施,只有被访问的控制块所处的位置在young区域1/4之后,才移动此控制块到young的头部,这样也能降低调整LRU链表的频率。

六、脏页刷新

​ Innodb有专门的后台线程每隔一段时间把有变动的缓存页,即存放在flush链表中控制块对应的缓存页,刷新到磁盘对应的页中,这样不影响系统正常的访问请求。刷新有三种方式:

1.BUF_FLUSH_LRU:从LRU链表的old区域刷新一部分控制块对应的缓存页到磁盘中,后台线程会定时从LRU链表的尾部开始扫描,一次扫描的数量由系统变量innodb_lru_scan_depth来定义,如果扫描到的控制块存在于flush链表,即控制块对应的缓存页有变动,则把缓存页的数据刷新到磁盘中。之所以选择从LRU链表old区域尾部去扫描刷新,是因为当有新的页需要添加到buffer pool中,而当前已经没有空闲缓存页,需要从LRU中去淘汰缓存页腾出空间,若是淘汰的缓存页有变动,则需要等此缓存页刷新到磁盘才能淘汰,这样等待的时间太久,异步定时刷新LRU中控制块对应缓存页有变动的数据到磁盘中,可以大大提高效率。

2.BUF_FLUSH_LIST:后台线程会定时从flush链表刷新一部分控制块对应的缓存页到磁盘中,flush链表中存的都是有变动的缓存页对应的控制块。

3.BUF_FLUSH_SINGLE_PAGE:当有新的页需要添加到buffer pool中,而当前已经没有空闲缓存页,会从LRU链表的old区域尾部淘汰一个控制块对应的缓存页,若是此控制块存在于flush链表中,表示此缓存页有变动还没有同步到磁盘,则需要先把此缓存页刷新到磁盘中,再从LRU中淘汰对应的控制块。

七、多个Buffer pool

​ Buffer pool是Innodb向操作系统申请的一块连续的内存空间,在多线程环境下,访问buffer pool的各种链表都需要加锁处理,在多线程并发的情况下,会严重影响请求的速度。Innodb支持同时使用多个buffer pool,每个buffer pool都称为一个实例,他们独立去申请内存空间,独立管理各种链表,在多线程并发访问的情况下互不影响,提高并发能力。

​ 多个buffer pool实例的效果图是这样的:
在这里插入图片描述
​ 可以在mysql服务启动的时候设置系统变量innodb_buffer_pool_instances的值来修改buffer pool的实例数。每个buffer pool实际占用的内存空间=innodb_buffer_pool_size / innodb_buffer_pool_instances。

​ 并不是buffer pool的实例数越多越好,因为管理每一个buffer pool都需要花销。innodb规定:当innodb_buffer_pool_size小于1G时,设置的innodb_buffer_pool_instances大于1会被重置为1,也就是说要想设置多个实例,innodb_buffer_pool_size的值要大于1G,innodb_buffer_pool_instances的最大值是64。

八、Chunk单位

​ 在mysql 5.7.5之前,buffer pool的大小只能在mysql启动之前通过innodb_buffer_pool_size设置,在mysql运行过程中是不允许进行修改的。在mysql5.7.5及之后的版本中支持运行过程中修改innodb_buffer_pool_size的值,每次修改都需要重新向服务器申请连续的内存空间,把旧的buffer pool数据放到新的buffer pool中,这样的操作是特别费时的。innodb使用chunk为单位来申请连续的内存空间,一个buffer pool由多个chunk组成,每个chunk里面存放控制块和缓存页对,一个chunk的默认大小是128M。这样在运行过程中修改buffer pool的大小,只用增加或者删除chunk的数量,使chunk的累计大小等于新的buffer pool值即可达到修改的效果。

​ chunk的效果图是这样的:
在这里插入图片描述
​ chunk的大小由系统变量innodb_buffer_pool_chunk_size来设置,不过chunk的大小只能在mysql系统启动之前设置,运行过程中是不允许设置的,因为在运行过程中设置会导致所有的buffer pool中的chunk都需要变动,都需要重新申请chunk,再把旧的chunk里的数据放到新chunk中,是非常耗时的。

配置chunk值需要注意的点:

1.innodb_buffer_pool_size的值必须是innodb_buffer_pool_chunk_size * innodb_buffer_pool_instances的整数倍,为了保证每个buffer pool的chunk数量相同;当innodb_buffer_pool_size不是他们两乘积的整数倍时,会自动把innodb_buffer_pool_size的值设置为他们乘积的整数倍。

2.当innodb_buffer_pool_chunk_size * innodb_buffer_pool_instances > innodb_buffer_pool_size时,一个buffer pool实例都分不到一个chunk的大小,此时会重新计算innodb_buffer_pool_chunk_size = innodb_buffer_pool_size/innodb_buffer_pool_instances 。

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

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

相关文章

web表格(详解)

目录 1.概述 2.表格的基本结构 3.表格的属性 4.单元格合并 1.概述 表格的基本语法结构&#xff1a; <table><tr><td>单元格内容</td>……</tr><tr><td>单元格内容</td>……</tr> </table> 其中< table>…

[附源码]SSM计算机毕业设计视屏网站论文JAVA

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

C++ Primer Plus第五版笔记(p51-100)

45 46 常量指针必须初始化 47 一条语句可以定义出不同类型的变量 int i10, *p&i,&r i; 48 应该是int p 而不是int p 49 **表示指向指针的指针 p52 50 指针是对象&#xff0c;所以存在对于指针的引用 int *p; int *&rp; 51 在默认状态下 &#xff0c;const对象只…

【教材】2022/11/27[指针] 指针与函数基础

程序&#xff1a;求10个数的最大数 1、定义指向函数的指针变量调用函数的方法 一般定义形式为&#xff1a;类型名 &#xff08;*指针变量名)()&#xff1b; #include<stdio.h> int main() {int i, m, a[10], max(int* p);int (*f)();for (i 0; i < 10; i)scanf_s(&q…

day7【代码随想录】移除链表元素

文章目录一、链表定义二、移除链表元素&#xff08;力扣203&#xff09;1、直接使用原来的链表来进行删除操作2、设置一个虚拟头结点在进行删除操作三、删除链表中的节点&#xff08;力扣237&#xff09;一、链表定义 public class ListNode {// 结点的值int val;// 下一个结点…

如何安装Jmeter监控服务器资源插件(JMeterPlugins + ServerAgent 方法二)?

一、服务器端插件 1、下载链接:https://pan.baidu.com/s/1Is1kuC656cB0mC4vOLHyhw?pwd12f1 提取码:12f1 &#xff08;或者这个下载服务器插件&#xff1a;ServerAgent 下载地址&#xff1a;https://github.com/undera/perfmon-agent&#xff09; 2、服务器端插件 将下载的Se…

Redis最全详解(一)——基础介绍

Redis介绍 redis是基于内存可持久化的日志型、Key-Value数据库。redis安装在磁盘&#xff0c;但是数据存储在内存。非关系型数据库NoSql。开源免费&#xff0c;遵守BSD协议&#xff0c;不用关注版权问题。 redis作者github&#xff1a;github.com/antirez redis是一种基于键…

【数字信号去噪】小波阙值数字信号去噪和求信噪比【含Matlab源码 2191期】

⛄一、小波阈值法去噪概述 电能质量扰动信号的噪声大多以高斯白噪声的形式存在&#xff0c;利用小波变换对信号进行多分辨率分解&#xff0c;由于小波变换具有去除数据相关性的特点&#xff0c;故可以将有用信号与噪声的能量分离开来。信号中有效的信息主要集中在较大的小波系…

[阶段4 企业开发进阶] 2. Redis--实战篇

文章目录实战篇1 短信登录1.1 导入项目导入SQL有关当前模型导入后端项目导入前端工程运行前端项目1.2 基于Session实现登录流程1.3 实现发送短信验证码功能1.4 实现登录校验拦截器1.5 隐藏用户敏感信息1.6 session共享问题实战篇 1 短信登录 1.1 导入项目 导入SQL 有关当前模…

多监控系统产生的告警如何高效管理 - 运维事件中心

随着互联网服务深入千行百业&#xff0c;数字化成为企业和机构为用户提供服务的重要形式。在企业的IT基础架构趋于复杂化的过程中&#xff0c;运维管理工作的技术性也有了更高的要求。如果针对相关的故障&#xff0c;企业无法做到及时的发现和响应&#xff0c;将会延长上层业务…

ElasticSearch中基础API操作

1:首先我们需要连接ElasticSearch客户端&#xff0c;需要一个连接操作&#xff1a; RestHighLevelClient package com.atguigu.es.test;import org.apache.http.HttpHost; import org.elasticsearch.client.RestClient; import org.elasticsearch.client.RestHighLevelClient;…

分布式全局唯一ID生成方案(附源码)

1、概述 ID&#xff0c;全称Identifier&#xff0c;中文翻译为标识符&#xff0c;是用来唯一标识对象或记录的符号。比如我们每个人都有自己的身份证号&#xff0c;这个就是我们的标识符&#xff0c;有了这个唯一标识&#xff0c;就能快速识别出每一个人。 在计算机世界里&…

客快物流大数据项目(八十八):ClickHouse快速入门

文章目录 ClickHouse快速入门 一、​​​​​​​​​​​​​​安装ClickHouse&#xff08;单机&#xff09; 1、安装yum-utils工具包 2、添加ClickHouse的yum源 3、安装ClickHouse的服务端和客户端 4、关于安装的说明 5、查看ClickHouse的版本信息 二、在命令行中操作…

【深度思考】5年开发经验,不知道git rebase,是否应该被嘲笑?

最近逛脉脉&#xff0c;发现了一个热度挺高的帖子&#xff0c;一位同学发帖说&#xff1a;同事5年经验&#xff0c;竟然不知道git rebase&#xff0c;真牛批 ☆ 今天咱们不讨论git rebase是干什么用的&#xff0c;因为讨论半天可能三两句话就说出来了&#xff0c;实在没有意思&…

Linux的进程互调技术(多语言互调)

Linux的进程互调技术(多语言互调) 文章目录Linux的进程互调技术(多语言互调)1.函数与进程之间的相似性2.多语言程序互调技术1.函数与进程之间的相似性 如果你学过C语言&#xff0c;你应该有以下认识&#xff1a; 一个C程序由很多函数组成&#xff0c;一个函数可以调用另一个函数…

STC 51单片机42——汇编 定时器 舵机

ORG 0000H HighH EQU 30H; 定义变量&#xff0c;高电平高八位 TimerH EQU 31H; 定义变量&#xff0c;高电平高八位定时值 TimerL EQU 32H; 定义变量&#xff0c;高电平低八位定时值 Right BIT P2.0 ; 右转 Left BIT P2.1 ; 左转 N…

IDEA搭建SSM框架【配置类、新手向】

以下操作基于2020.3企业版 1.创建Java Enterprise项目 直接默认即可 输入项目相关信息 点击完成后&#xff0c;得到以下目录结构 2.搭建项目目录结构 java目录下 controller&#xff1a;实现控制转发&#xff0c;基本参数校验&#xff0c;不复杂的简单业务处理 config&#x…

SpringBoot实现多数据源(一)【普通版切换】

在实际开发中&#xff0c;经常可能遇到在一个应用中可能需要访问多个数据库的情况。以下是两种典型场景 业务复杂&#xff08;数据量大&#xff09; 数据分布在不同的数据库中&#xff0c;数据库拆了&#xff0c;应用没拆。一个公司多个子项目&#xff0c;各用各的数据库&#…

Springboot——拦截器

目录 一、拦截器概念 二、拦截器的使用 2.1 拦截器的创建&#xff08;preHandle实用性最强&#xff09; 2.2 将拦截器添加到容器当中 三、拦截器参数 3.1 获取请求头 request.getHeader 3.2 Object handler 是什么参数 3.3 ModelAndView modelAndView 3.4 Exception ex 3.…

多重定义的全局符号,链接器会如何链接的情况

多重定义的全局符号&#xff0c;链接器会如何链接的情况实例1&#xff1a;1.规则12.规则13.规则24.规则3实例2总结以下只针对于gcc编译器&#xff0c;而且不同环境&#xff0c;不同编译器的情况可能不同。 假如说有多重定义的全局符号&#xff0c;链接器会如何链接呐&#xff…