MySQL: Buffer Pool概念整理

devtools/2024/10/21 9:58:30/

一. 简介

MySQL中的Buffer Pool是InnoDB存储引擎用来缓存表数据和索引的内存区域。这是InnoDB性能优化中最关键的部分之一。通过在内存中缓存这些数据,InnoDB可以极大减少对磁盘I/O的需求,因为从内存中读取数据远比从磁盘读取要快得多。因此,Buffer Pool的大小和管理方式直接影响到数据库的性能。

在这里插入图片描述

Buffer Pool的主要功能

  1. 数据缓存:当表中的数据被读取时,InnoDB首先检查这些数据是否已在Buffer Pool中。如果是,就直接从内存中读取数据,避免了磁盘I/O操作。如果不是,InnoDB会从磁盘读取数据,并将其缓存到Buffer Pool中,以供后续请求使用。

  2. 索引缓存:InnoDB使用B+树索引,这些索引同样被缓存在Buffer Pool中。这意味着数据的查找和访问也可以极大地加速,因为索引查找通常是数据库操作中最频繁执行的操作之一。

  3. 脏页的写回:当InnoDB中的数据被修改(如通过INSERT、UPDATE或DELETE语句)时,这些更改首先在Buffer Pool中的数据页(即“脏页”)上进行。然后,这些脏页会根据InnoDB的刷新策略异步地写回磁盘。这个过程称为CheckPointing,可以有效地减少对磁盘的写操作,并且提升事务的处理速度。

  4. 读预读:InnoDB的Buffer Pool还会根据数据的访问模式自动预读数据页到Buffer Pool中。这种预读操作可以减少未来读操作的等待时间,从而提高数据库性能。

Buffer Pool的配置

Buffer Pool的大小是影响InnoDB性能的最重要的配置之一。它通过innodb_buffer_pool_size配置参数设置。 默认值为 128M

  • my.cnf
[server]innodb_buffer_pool_size = 2147483648
  • 查看参数
mysql> SHOW VARIABLES LIKE 'innodb_buffer_pool_size';
+-------------------------+-----------+
| Variable_name           | Value     |
+-------------------------+-----------+
| innodb_buffer_pool_size | 134217728 |
+-------------------------+-----------+
1 row in set (0.00 sec)

理想情况下,Buffer Pool的大小应该足以容纳绝大多数的数据库工作集(即活跃数据和索引)。这样可以确保大部分的数据库读操作都能从内存中完成,极大地提高性能。

在配置Buffer Pool时,需要考虑到服务器上可用的物理内存大小。通常建议将Buffer Pool的大小设置为系统物理内存的50%-80%,但这也取决于服务器上运行的其他应用程序及其内存需求。

从MySQL 5.5版本开始,Buffer Pool可以被配置为多个实例,这有助于提高并发性能,因为它减少了多个线程在访问Buffer Pool时的锁争用。

二. 名词解释

数据页

Buffer Pool中存放的是一个一个的数据页.

MySQL对数据抽象出来了一个数据页的概念, 多行数据放在了一个数据页里.
这样磁盘文件中就是会有很多的数据页,每一页数据里放了很多行数据
假设我们要更新一行数据,此时数据库会找到这行数据所在的数据页,然后从磁盘文件里把这行数据所在的数据页直接给加载到Buffer Pool里去

Buffer Pool中的缓存页

在MySQL的InnoDB存储引擎中,Buffer Pool是一个非常重要的内存区域,用于缓存数据和索引,从而减少对磁盘的I/O操作,提高数据库操作的速度。在这个上下文中,"缓存页"是Buffer Pool中的基本单位。

缓存页的概念

  • 页(Page):InnoDB存储引擎以页为单位管理数据和索引,一页通常是16KB大小(这是默认值,但可以配置)。无论是数据行还是索引条目,都存储在这些页中。

  • 缓存页:当数据被读取(查询)或修改(插入、更新、删除等)时,相关的数据页和索引页会从磁盘加载到Buffer Pool中,这时,这些页就成为了"缓存页"。这意味着它们被缓存于内存中,以便快速访问。

实际上默认情况下, 磁盘中存放的数据页的大小是16KB,也就是说,一页数据包含了16KB的内容。

而Buffer Pool中存放的一个一个的数据页,我们通常叫做缓存页,因为毕竟Buffer Pool是一个缓冲池,里面的数据都是从磁盘缓存到内存去的。

而Buffer Pool中默认情况下,一个缓存页的大小和磁盘上的一个数据页的大小是一一对应起来的,都是16KB。

  • 缓存页的类型
    脏页(Dirty Page):当一个缓存页上的数据被修改后,该页就变成了脏页。脏页表示它包含的数据与磁盘上的原始数据不同步。InnoDB定期将这些页写回磁盘,以保持数据的持久性和一致性。
    干净页(Clean Page):如果一个缓存页上的数据没有被修改,或者虽然被修改但已经被写回磁盘,那么这个页就是干净页。干净页可以在需要为新的数据页腾出空间时无损删除。

  • 缓存页的作用
    提高数据访问速度:通过将热点数据和索引页缓存在内存中,Buffer Pool可以极大地减少对磁盘的读写需求,从而提高查询和事务处理的速度。
    优化数据写入性能:通过合并多个写操作(即将多次对同一数据页的修改累积在一次写回磁盘操作中),减少了磁盘I/O的次数,提高了写入性能。

  • InnoDB使用一种复杂的算法来管理Buffer Pool中的页,包括:

页的加载:当需要访问的数据不在Buffer Pool中时,InnoDB会从磁盘中加载相应的数据页到Buffer Pool中。

页的替换:当Buffer Pool满时,InnoDB需要替换旧的页来为新页腾出空间。InnoDB通常使用LRU(最近最少使用)算法来决定哪些页被替换。

脏页的刷新:InnoDB不断地将脏页异步写回磁盘,以确保数据的持久性和减少系统崩溃时数据丢失的风险。

缓存页对应的描述信息

在MySQL的InnoDB存储引擎中,每个缓存页(即在Buffer Pool中缓存的数据页或索引页)都有对应的描述信息,这些信息帮助InnoDB有效管理和使用Buffer Pool。这些描述信息包括但不限于:

  1. 页号(Page Number):这是缓存页在其所属的表空间(Tablespace)中的唯一标识符。页号允许InnoDB快速定位并访问特定的缓存页。

  2. LSN(Log Sequence Number):每个缓存页包含一个LSN,标识了页的最新修改版本。LSN是InnoDB重做日志(Redo Log)中一个递增的序列号,用于跟踪数据的修改。当一个页被加载到Buffer Pool中,或者在Buffer Pool中被修改时,该页的LSN会被更新。LSN对于数据恢复和确保数据页与重做日志同步至关重要。

  3. 脏标志(Dirty Mark):如果缓存页自从被加载到Buffer Pool中后发生了修改,就会被标记为“脏页”(Dirty Page)。脏标志指示该页包含未被写回磁盘的修改,需要在适当时机通过CheckPointing机制写回磁盘,以确保数据的持久性。

  4. 访问时间戳(Access Time Stamp):记录了缓存页最后一次被访问(读取或修改)的时间。这个时间戳是管理Buffer Pool中页的替换策略的重要因素,特别是在使用最近最少使用(LRU)算法时,可以根据访问时间戳来确定哪些页应该被保留,哪些页应该被替换出Buffer Pool。

  5. 固定计数(Fix Count):表示当前有多少操作正在访问该页。如果一个页被固定(即Fix Count大于0),则它不能被替换出Buffer Pool,因为有操作依赖于这个页的数据。页的固定状态确保了数据的一致性和稳定性。

  6. 哈希索引(Hash Index):为了快速查找Buffer Pool中的页,InnoDB维护了一个哈希表,将页号映射到Buffer Pool中页的具体位置。这使得根据页号快速定位页成为可能。

在Buffer Pool中,每个缓存页的描述数据放在最前面,然后各个缓存页放在后面
Buffer Pool中的描述数据大概相当于缓存页大小的5%左右,也就是每个描述数据大概是800个字节左右的大小,然后假设你设置的buffer pool大小是128MB,实际上Buffer Pool真正的最终大小会超出一些,可能有个130多MB的样子,因为他里面还要存放每个缓存页的描述数据。

free链表

free链表本质上是一种链表数据结构,用于链接所有被释放的、尚未被重新使用的内存块。每个节点代表一个空闲的内存块,这些节点按照一定规则(如内存块的大小、地址等)组织起来,形成链表。这样当MySQL需要分配内存时,可以通过遍历free链表,快速找到合适大小的空闲内存块进行重用,如果没有合适的内存块,再从内存池中分配新的内存空间。

free链表里面就是各个缓存页的描述数据块,只要缓存页是空闲的,那么他们对应的描述数据块就会加入到这个free链表中,每个节点都会双向链接自己的前后节点,组成一个双向链表
free链表有一个基础节点,他会引用链表的头节点和尾节点,里面还存储了链表中有多少个描述数据块的节点,也就是有多少个空闲的缓存页。
free链表,他本身其实就是由Buffer Pool里的描述数据块组成的,你可以认为是每个描述数据块里都有两个指针,一个是free_pre,一个是free_next,分别指向自己的上一个free链表的节点,以及下一个free链表的节点。
对于free链表而言,只有一个基础节点是不属于Buffer Pool的,他是40字节大小的一个节点,里面就存放了free链表的头节点的地址,尾节点的地址,还有free链表里当前有多少个节点。

  • 使用free链表的好处包括:

减少内存碎片:通过重用空闲内存块,减少了内存碎片的产生。
提高内存分配效率:直接从free链表中找到合适的内存块进行分配,避免了每次都从内存池分配新的内存块的开销。
简化内存管理:通过统一的free链表来管理所有的空闲内存块,简化了内存的管理逻辑。

flush链表

flush链表本质也是通过缓存页的描述数据块中的两个指针,让被修改过的缓存页的描述数据块,组成一个双向链表。

凡是被修改过的缓存页,都会把他的描述数据块加入到flush链表中去,flush的意思就是这些都是脏页,后续都是要flush刷新到磁盘上去的

LRU

MySQL中使用的InnoDB存储引擎实现了一种改良的最近最少使用(LRU, Least Recently Used)算法来管理其Buffer Pool中页(数据和索引页)的缓存。这个改良的LRU算法旨在平衡新加入的页和频繁访问的页之间的关系,确保热门数据能够留在内存中,同时给新数据一定的机会证明其是否也可能成为热门数据。以下是InnoDB LRU算法的具体实现细节:

基础LRU列表

  • LRU列表:InnoDB的Buffer Pool维护一个LRU列表来记录每个页的使用情况。当一个页被访问时(无论是读取还是写入),如果它已经在Buffer Pool中,则它会被移动到LRU列表的前端,表示它最近被使用过。如果一个新页被读取进来且Buffer Pool已满,那么LRU列表末尾(即最长时间未被使用的页)将被移除以腾出空间。

改良策略

  1. 分区:InnoDB将其LRU列表分为两个部分,一个是用于存储最近访问较多的“热”页的“young”区域,另一个是用于存储其他页的“old”区域。默认情况下,“young”区域占LRU列表的约63%,而“old”区域占37%。
mysql> SHOW VARIABLES LIKE 'innodb_old_blocks_pct';
+-----------------------+-------+
| Variable_name         | Value |
+-----------------------+-------+
| innodb_old_blocks_pct | 37    |
+-----------------------+-------+
1 row in set (0.00 sec)
  1. 中点插入策略(Mid-Point Insertion Strategy):在InnoDB中,新读取的页不是直接被放到LRU列表的最前端,而是被插入到“old”和“young”区域的分界点。这种方法给新页一个机会,让它们在被确认为不够“热门”而从列表中移除之前,有一段时间可以被访问和使用。如果这些页在“old”区域中被访问,它们会被移动到LRU列表的前端,即“young”区域。

  2. 访问控制:为了进一步控制页在LRU列表中的移动,InnoDB引入了一个机制,其中只有当一页在“old”区域被访问的次数超过一定阈值时,这个页才会被移动到“young”区域。这个阈值可以通过innodb_old_blocks_time参数设置,该参数指定一个页必须在“old”区域中停留的最小时间(以毫秒为单位),在此期间页的访问不会导致它被移动到“young”区域。

总结

InnoDB通过这种改良的LRU算法有效地管理Buffer Pool中的数据和索引页,既保证了频繁访问的热门数据能够快速被访问,又为新页提供了一定的空间和机会展示其重要性。这种平衡策略对于数据库的性能优化至关重要,有助于提高缓存效率和查询性能。

MySQL的预读机制

MySQL的预读机制主要与其底层存储引擎的实现有关,尤其是InnoDB存储引擎。预读(Pre-reading)或预取(Prefetching)是一种性能优化技术,其中数据库系统主动读取可能很快就会被查询到的数据页到缓冲池(Buffer Pool)中,即使这些数据页此刻还没有被直接请求。这样做的目的是减少等待I/O操作完成的时间,从而提高查询性能。

在MySQL的InnoDB存储引擎中,预读机制可以在以下情况下被触发:

    1. 顺序扫描

当InnoDB检测到对表的顺序扫描操作时,它可能会预读更多的数据页到缓冲池中。顺序扫描通常发生在全表扫描或索引扫描时,此时InnoDB会预测接下来会访问哪些数据页,并尝试提前将它们加载到内存中。

    1. 范围查询

在对索引进行范围查询时(如使用BETWEEN><等操作符),如果数据库发现查询正在按顺序访问大量连续的数据页,它可能会启动预读,因为系统推测接下来的查询将继续沿着这个范围进展。

    1. 索引扫描

在进行索引扫描时(尤其是对主键或唯一索引的扫描),如果扫描操作按照顺序访问索引页,InnoDB也可能会执行预读,尝试提前加载可能会被访问的数据页。

    1. 背景预读

InnoDB也有一些背景操作,可能在系统空闲时自动进行数据页的预读。这些操作依赖于InnoDB的内部调度和优化决策,目的是优化数据在缓冲池中的布局,提高缓冲池的命中率。

  • 配置和限制

值得注意的是,预读的行为可以通过一系列的配置参数来调整,例如innodb_read_ahead_threshold,这个参数用来设置InnoDB启动线性预读操作之前要观察到的顺序访问的页面数量。此外,操作系统和硬件的能力也影响预读的效率,例如磁盘的I/O性能和操作系统的文件系统缓存策略。

总的来说,MySQL(特别是InnoDB存储引擎)的预读机制旨在通过智能预测即将需要的数据,提前将这些数据加载到内存中,以减少数据库操作的磁盘I/O需求,从而提高整体的查询性能。然而,过度的预读可能会消耗过多的系统资源,因此需要根据具体的工作负载和系统配置适当调整。

  • innodb_read_ahead_threshold 默认值为56
    如果顺序的访问了一个区里的多个数据页,访问的数据页的数量超过了这个阈值,此时就会触发预读机制,把下一个相邻区中的所有数据页都加载到缓存里去
mysql> SHOW VARIABLES LIKE 'innodb_read_ahead_threshold';
+-----------------------------+-------+
| Variable_name               | Value |
+-----------------------------+-------+
| innodb_read_ahead_threshold | 56    |
+-----------------------------+-------+
1 row in set (0.01 sec)
  • innodb_random_read_ahead 默认关闭

如果Buffer Pool里缓存了一个区里的13个连续的数据页,而且这些数据页都是比较频繁会被访问的,此时就会直接触发预读机制,把这个区里的其他的数据页都加载到缓存里去
这个机制是通过参数innodb_random_read_ahead来控制的,他默认是OFF,也就是这个规则是关闭的

mysql> SHOW VARIABLES LIKE 'innodb_random_read_ahead';
+--------------------------+-------+
| Variable_name            | Value |
+--------------------------+-------+
| innodb_random_read_ahead | OFF   |
+--------------------------+-------+
1 row in set (0.00 sec)

冷热数据分离的思想设计LRU链表

真正的LRU链表,会被拆分为两个部分,一部分是热数据,一部分是冷数据,这个冷热数据的比例是由innodb_old_blocks_pct参数控制的,他默认是37,也就是说冷数据占比37%。

  • 查看配置参数
mysql> SHOW VARIABLES LIKE 'innodb_old_blocks_pct';
+-----------------------+-------+
| Variable_name         | Value |
+-----------------------+-------+
| innodb_old_blocks_pct | 37    |
+-----------------------+-------+
1 row in set (0.00 sec)

所以MySQL设定了一个规则,他设计了一个innodb_old_blocks_time参数,默认值1000,也就是1000毫秒

mysql>
mysql>
mysql> SHOW VARIABLES LIKE 'innodb_old_blocks_time';
+------------------------+-------+
| Variable_name          | Value |
+------------------------+-------+
| innodb_old_blocks_time | 1000  |
+------------------------+-------+
1 row in set (0.00 sec)

也就是说,必须是一个数据页被加载到缓存页之后,在1s之后,你访问这个缓存页,他才会被挪动到热数据区域的链表头部去。

因为假设你加载了一个数据页到缓存去,然后过了1s之后你还访问了这个缓存页,说明你后续很可能会经常要访问它,这个时间限制就是1s,因此只有1s后你访问了这个缓存页,他才会给你把缓存页放到热数据区域的链表头部去。


http://www.ppmy.cn/devtools/38666.html

相关文章

NSSCTF Web方向的例题和相关知识点(一)

[SWPUCTF 2021 新生赛]jicao 解题&#xff1a; 打开环境&#xff0c;是一段php代码 包含了flag.php文件&#xff0c;设定了一个POST请求的id和GET请求的json 语句会对GET请求的数据进行json解码 如果id和json变量的值都等于设定字符串&#xff0c;则得到 flag 我们可以使用…

MATLAB 代数

MATLAB 代数 到目前为止&#xff0c;我们已经看到所有示例都可以在MATLAB及其GNU&#xff08;也称为Octave&#xff09;中运行。但是&#xff0c;为了求解基本的代数方程&#xff0c;MATLAB和Octave几乎没有什么不同&#xff0c;因此我们将尝试在单独的部分中介绍MATLAB和Octa…

一款开源高性能AI应用框架

前言 LobeChat 是一个基于 Next.js 框架构建的 AI 会话应用&#xff0c;旨在提供一个 AI 生产力平台&#xff0c;使用户能够与 AI 进行自然语言交互。 LobeChat应用架构 LobeChat 的整体架构由前端、EdgeRuntime API、Agents 市场、插件市场和独立插件组成。这些组件相互协作&a…

[1726]java试飞任务规划管理系统Myeclipse开发mysql数据库web结构java编程计算机网页项目

一、源码特点 java试飞任务规划管理系统是一套完善的java web信息管理系统&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为 TOMCAT7.0,Myeclipse8.5开发&#xff0c;数据库为Mysql…

【工具】如何提取一个mp4文件的关键帧

文章目录 怎么做如何安装ffmepgUbuntu 或 DebianCentOS 或 FedoramacOSWindows其他 Linux 发行版 实践什么是关键帧 怎么做 你可以使用ffmpeg这个强大的多媒体处理工具来提取mp4文件中的关键帧。以下是一个示例命令&#xff0c;可以使用ffmpeg从mp4文件中提取关键帧&#xff1…

CSS3新增特性

新增属性选择器 <style>/* 必须是input但是同时具有value这个属性选择这个元素 [] */input[value] {color: red;}input[typetel] {color: blue;}/* 选择首先是div然后具有cLass属性并且属性值必须是 icon开头的这些元素 ^ */div[class^icon] {color: pink;}section[c…

独孤思维:赚美金,新项目正式发布

独孤四年前开始日更写作以前&#xff0c;还做过海外赚美金项目。 当时图便宜&#xff0c;报名了国外联盟-海外问卷这个赛道。 授课老师&#xff0c;给了我一个信息表&#xff0c;让我搞了100个guge账号。 开始矩阵注册各站点&#xff0c;矩阵生成油管人身份信息。 第一阶…

Python | Leetcode Python题解之第67题二进制求和

题目&#xff1a; 题解&#xff1a; class Solution:def addBinary(self, a, b) -> str:return {0:b}.format(int(a, 2) int(b, 2))