【MySQL性能优化】- 一文轻松学会BufferPool缓存机制
2025-02-15 08:36 阅读(60)

https://www.zuocode.com

关注公众号:程序员老左   发现更多新技术

前言

上一篇文章《MySQL结构与SQL执行过程》介绍了MySQL的结构以及执行的过程,接下来就来介绍其在InnoDB中BufferPool的缓存机制。本文主要介绍了MySQL中InnoDB存储引擎的Buffer Pool缓存机制及其与日志文件的协同工作原理。Buffer Pool是InnoDB的核心组件,通过缓存数据和索引页,减少磁盘I/O操作,提升数据库性能。文章还详细介绍了三种日志文件(Undo Log、Redo Log和Bin Log)的作用、区别以及它们在事务处理中的协同工作流程。

BufferPool概念

MySQL的Buffer Pool(缓冲池)是InnoDB存储引擎的核心组件,负责在内存中缓存数据和索引,减少磁盘I/O操作,大幅提升数据库性能。BufferPool 包含了磁盘中的部分页数据,是作为访问数据库的一个缓冲池,作为内存缓存区,存储频繁访问的数据页(Data Page)和索引页(Index Page),并且所有读写操作优先在Buffer Pool中进行,避免直接访问磁盘。磁盘I/O速度比内存慢几个数量级,Buffer Pool通过减少磁盘访问,成为MySQL性能优化的关键。

当我们从数据库读取一条数据时,MySQL 会首先在Buffer Pool 中查找这个数据,如果找到了,那么就直接从Buffer Pool中读取数据。如果没有找到,MySQL 就会从磁盘中读取数据,并将数据存储到Buffer Pool 中,这样下次查询同样的数据时,就可以直接从缓存中获取,而不需要再次从磁盘中读取。

BufferPool工作原理

BufferPool 的最小单位是 Page,这是数据库中的一个页面,大小可能是 4KB 或 8KB。(bufferpool 一般设置机器内存的 60%左右)

数据读取流程

当 MySQL 读取数据的时候,首先 InnoDB 会先检查 BufferPool 是否存在对应的页。如果存在的话,就会直接返回内存中的数据;如果不存在,则会先从磁盘中读取页到 BufferPool 在返回数据。

数据写入流程

当 MySQL 写/修改数据的时候,首先会更新 BufferPool 的页,此时页变为脏页。脏页会由后台线程(如Checkpoint机制)异步刷回磁盘,确保数据最终持久化。

缓存淘汰策略

使用改进的LRU算法管理内存,将LRU链表分为Young区(热数据)和Old区(冷数据)。新加载的页先插入Old区头部,若在指定时间(innodb_old_blocks_time)后被再次访问,才移动到Young区。这样做的优势是避免全表扫描等短期大量操作污染热数据。

三种日志文件

在数据库系统中,undo log、redo log 和 bin log 是三种关键的日志机制,分别承担不同角色,共同保障事务的 ACID 特性(原子性、一致性、隔离性、持久性)。

Undo Log(回滚日志)

UndoLog 逻辑日志,主要用于事务的回滚和MVCC。当用户执行一个事务时,如果事务需要回滚,undo log会记录修改前的数据,这样就可以恢复到之前的状态。另外,MVCC需要读旧版本的数据,undo log也能提供支持。比如,InnoDB引擎用undo log来实现事务的回滚和一致性读。


存储位置:InnoDB 引擎层维护,存储在 表空间 或独立的 undo 文件中。

日志类型:逻辑日志(记录反向操作,如 INSERT 对应 DELETE)。

生命周期:事务结束后,若没有其他事务依赖其旧版本数据,undo log 会被清理。



事务更新UPDATE t SET name='Bob' WHERE id=1,undo log 会记录 name='Alice'(旧值),以便回滚或供其他事务读取历史版本。


Redo Log(重做日志)

RedoLog 物理日志,主要是为了崩溃恢复。在事务提交的时候,数据页可能还没写入磁盘,这时候如果系统崩溃,redo log可以重放这些操作,确保持久性。redo log是物理日志,记录的是页的修改,顺序写入,所以写入速度比较快。通常有固定大小,循环覆盖使用。事务提交时先写入 redo log(Write-Ahead Logging, WAL 机制),再异步刷脏页到磁盘。

*如果事务提交成功,但是bufferpool还没来得及写入磁盘,此时系统宕机了,这时候就可以使用redo日志的数据来恢复bufferpool的缓存数据。


事务提交后,即使数据页未刷盘,只要 redo log 落盘,重启后可通过 redo log 重放恢复数据。


Bin Log(二进制日志)

二进制日志,属于MySQL Server层的,主要用来主从复制和数据恢复。它记录的是逻辑操作,比如执行的SQL语句。bin log有三种模式,statement记录SQL,row记录行变化,mixed混合两种。主从同步的时候,从库通过bin log来重放操作,保持数据一致。事务提交后顺序写入 bin log,通过sync_binlog  参数控制刷盘策略。


主库执行 DELETE FROM t WHERE id=1,bin log 记录该操作(或具体行变化),从库读取后重放。


三者的区别

undo log和redo log是InnoDB引擎层的,而bin log是MySQL Server层的。undo log用于回滚和MVCC,redo log保证持久性和崩溃恢复,bin log用于复制和恢复。事务提交时,redo log先写,bin log后写,两阶段提交保证一致性。

特性Undo LogRedo LogBin Log
层级InnoDB 引擎层InnoDB 引擎层 MySQL Server 层
日志类型 逻辑日志(反向操作) 物理日志(页修改) 逻辑日志(SQL/行变更)
目的回滚事务、MVCC 崩溃恢复、持久性 主从复制、数据恢复
生命周期 短(事务结束后清理)长(覆盖循环写入) 长(可配置过期时间)

以更新为例,简单由以下三个步骤来实现:


事务执行 UPDATE:


生成 undo log(记录旧值)。

修改内存数据页,生成 redo log(记录新值)。



事务提交:


redo log 刷盘(prepare 状态)。

bin log 刷盘。

redo log 标记为 commit  状态(两阶段提交,保证一致性)。



后台线程异步将脏页刷盘,清理 undo log。


案例分析

接下来我们通过一个案例来分析整个流程是怎么流转的,我们通过一句更新语句来讲解案例。

update user set name = 'lydabc' wherer id = 1

首先先看一下以下的图,这是 MySQL 的执行流程。

我们主要看下部分,我们知道每次增删改查首先到达的就是 BufferPool 查询,没有找到的情况才去磁盘文件中获取。当执行更新语句的时候,MySQL 并不是直接将数据修改到磁盘文件中的。执行流程如下:


首先会先加载缓存数据,将id=1的所在的磁盘文件中的整页数据加载到缓存池中。(这里需要注意加载的数据是整页整页加载的,此时 bufferpool 的值 name=lyd)

InnoDB 引擎会把更新前的旧值写入 Undo Log 文件中(此时里面的值是name=lydabc),这是为了方便数据回滚。

接着就是执行器来更新内存数据(此时 bufferpool 的值 name=lydabc),但是磁盘文件目前还不是。

写入 Redo 日志,这里的写入并不是一条一条,而是批量的写入。

在准备提交事务的时候,redo log buffer 会真正写入磁盘(Redo Log 文件)。redo log 文件是 innoDB 持有的,如果事务提交成功,但是bufferpool还没来得及写入磁盘,此时系统宕机了,这时候就可以使用redo日志的数据来恢复bufferpool 的缓存数据。(此时 redo log 的值 name=lydabc)

准备提交事务 binlog 写入磁盘。这里写入到 binlog 文件,这是MySQL Server 层维护,独立于存储引擎(如 mysql-bin.000001)。(此时 bin log 的值 name=lydabc),bin日志主要是用来恢复数据库磁盘里的数据。

当 binlog 写完之后,会写入commit标记到redo日志文件中,提交事务完成(该标记是为了确保事务提交之后,redo日志与bin日志文件是一致的。)

当事务提交完成之后,磁盘文件的数据并没有直接更新,更新的时机是不固定的,是通过异步刷回磁盘,以page为单位,通过 IO 线程来随机写入磁盘,这步操作完毕之后,磁盘的name=lyd就会改成lydabc。


经过这些复杂流程,数据才算真正更新完毕。

总结

文章详细介绍了MySQL中Buffer Pool的缓存机制及其与三种日志文件(Undo Log、Redo Log和Bin Log)的协同工作流程。Buffer Pool通过缓存数据页和索引页减少磁盘I/O,提升性能;而三种日志文件分别负责事务的回滚、崩溃恢复和主从复制等功能。通过这些机制,MySQL能够高效地处理事务并保证数据的一致性和持久性。


作者:怒放吧德德

链接:https://juejin.cn