MySQL 三大日志(binlog、redo log 和 undo log)深度解析
- [1. redo log](#1. redo log)
-
- [redo log 的核心定位与工作原理](#redo log 的核心定位与工作原理)
- [redo log 的刷盘时机](#redo log 的刷盘时机)
- 日志文件组与环形写入
- [2. binlog](#2. binlog)
-
- [binlog 的核心定位与本质特征](#binlog 的核心定位与本质特征)
- [binlog 的记录格式](#binlog 的记录格式)
- [binlog 的写入与刷盘机制](#binlog 的写入与刷盘机制)
- [刷盘时机:由 sync_binlog 参数控制](#刷盘时机:由 sync_binlog 参数控制)
- [3. 两阶段提交](#3. 两阶段提交)
-
- [redo log 与 binlog 的一致性难题](#redo log 与 binlog 的一致性难题)
- 两阶段提交的核心逻辑
- [4. undo log](#4. undo log)
-
- [undo log 的核心作用](#undo log 的核心作用)
- [undo log 与 MVCC 的深度绑定](#undo log 与 MVCC 的深度绑定)
在 MySQL 数据库的运行体系中,日志系统是保障数据可靠性、一致性和可恢复性的核心组件。其中,redo log(重做日志)、binlog(归档日志)和 undo log(回滚日志)作为三大核心日志,各自承担着不同的关键角色
1. redo log
redo log 的核心定位与工作原理
redo log 是 InnoDB 存储引擎独有的物理日志,是 MySQL 具备崩溃恢复能力的核心
比如 MySQL 实例挂了或宕机了,重启时,InnoDB存储引擎会使用redo log恢复数据,保证数据的持久性与完整性

要理解 redo log 的工作流程,需先明晰 MySQL 的数据读写机制:
MySQL 中数据以 "页" 为基本单位(默认 16KB),当查询数据时,会先从磁盘加载对应数据页到内存中的 Buffer Pool(缓冲池);后续查询优先从 Buffer Pool 命中,未命中再读取磁盘,以此减少磁盘 IO 开销,提升读写性能。
数据更新操作同样遵循这一逻辑:若 Buffer Pool 中存在待更新的数据页,会直接在内存中修改,同时将 "数据页的修改行为" 记录到 redo log buffer(重做日志缓存),再通过特定策略刷盘到 redo log 文件中。
相较于直接将修改后的数据页刷盘(是随机 IO,性能极低),写入 redo log(是顺序 IO)的速度极快,既保证了更新操作的高效性,又通过日志留存了恢复数据的依据。

redo log 的刷盘时机
redo log 的刷盘策略直接决定了数据安全性与数据库性能的平衡,InnoDB 通过innodb_flush_log_at_trx_commit参数提供了三种核心策略,同时辅以后台线程的自动刷盘机制:
- 0 :设置为 0 的时候,表示每次事务提交时不进行刷盘操作
- 1 :设置为 1 的时候,表示每次事务提交时都将进行刷盘操作 (默认值)
- 2 :设置为 2 的时候,表示每次事务提交时都只把 redo log buffer 内容写入 page cache
另外,InnoDB 存储引擎有一个后台线程,每隔1 秒,就会把 redo log buffer 中的内容写到文件系统缓存(page cache),然后调用 fsync 刷盘。

也就是说,一个没有提交事务的 redo log 记录,也可能会刷盘
除了后台线程每秒1次的轮询操作,还有一种情况,当 redo log buffer 占用的空间即将达到 innodb_log_buffer_size 一半的时候,后台线程会主动刷盘,避免缓存溢出
innodb_flush_log_at_trx_commit=0
为0时,如果MySQL挂了或宕机可能会有1秒数据的丢失

innodb_flush_log_at_trx_commit=1
为1时, 只要事务提交成功,redo log记录就一定在硬盘里,不会有任何数据丢失。
如果事务执行期间MySQL挂了或宕机,这部分日志丢了,但是事务并没有提交,所以日志丢了也不会有损失。

innodb_flush_log_at_trx_commit=2
为2时, 只要事务提交成功,redo log buffer中的内容只写入文件系统缓存(page cache)。
如果仅仅只是MySQL挂了不会有任何数据丢失,但是宕机可能会有1秒数据的丢失。

日志文件组与环形写入
磁盘上的 redo log 并非单个文件,而是以 "日志文件组 " 的形式存在,组内每个日志文件大小固定(可通过innodb_log_file_size配置,通常建议设置为 1-4GB),且数量由innodb_log_files_in_group(默认 2)指定。
redo log 的写入采用 "环形数组" 模式:从第一个文件的起始位置开始顺序写入,写满一个文件后切换到下一个,直到写满最后一个文件,再回到第一个文件从头覆盖。这种设计既保证了日志写入的顺序性(顺序 IO),又通过固定大小的文件组控制了磁盘占用,同时 InnoDB 会记录 "检查点"(checkpoint)和 "当前写入点"(write pos):
write pos:当前正在写入的位置,推进到文件末尾则回到头部;checkpoint:表示已完成数据刷盘、可以覆盖的日志位置;- 两者之间的区域为 "可写入日志区",若
write pos追上checkpoint,InnoDB 会暂停写入,先推进checkpoint(将已记录的日志对应的脏页刷盘),再继续写入。

2. binlog
binlog 的核心定位与本质特征
不管用什么存储引擎,只要发生了表数据更新,都会产生
binlog日志
与 redo log(InnoDB 专属、物理日志 )不同,binlog 是 MySQL Server 层的逻辑日志 ,不依赖存储引擎 ------ 无论使用 InnoDB、MyISAM 还是其他引擎,只要发生表数据变动,都会生成 binlog。其核心价值在于:
- 数据备份:通过
binlog可以恢复指定时间段内的数据; - 集群同步:主备、主从、主主架构中,从库通过解析主库的 binlog 同步数据,保证集群数据一致性;
- 数据审计:可通过 binlog 追溯数据修改的历史操作。
binlog 的特性体现在:它记录的是修改数据的 "原始逻辑",而非数据页的物理变化,例如 "给 ID=2 的行的 c 字段加 1""插入一条 id=10、name='test' 的记录",而非 "某个数据页的第 N 个字节从 X 改为 Y"。同时,binlog 采用顺序写入模式,避免了随机 IO,保证了日志写入的高效性。
可以说MySQL数据库的数据备份、主备、主主、主从 都离不开binlog,需要依靠binlog来同步数据,保证数据一致性。

binlog 的记录格式
binlog 通过binlog_format参数支持三种记录格式,分别适配不同的业务场景:
- statement
- row
- mixed
1、statement 格式:记录 SQL 语句原文
- 逻辑:直接记录执行的 SQL 语句,例如执行
update T set update_time=now() where id=1,binlog 会完整记录这条 SQL - 优点:日志体积小,写入速度快,占用磁盘空间少
- 缺点:存在 "数据不一致风险"------ 如 SQL 中包含
now()、rand()等非确定性函数,或依赖会话变量、存储过程时,从库执行这条 SQL 会得到与主库不同的结果
2、row 格式:记录数据的实际修改
为了解决 statement 格式 的问题,我们需要指定为row
- 逻辑:不记录 SQL 语句,而是记录 "修改前的行数据" 和 "修改后的行数据",例如上述 update 语句,会记录 "id=1 的行,update_time 修改前为 2024-01-01 10:00:00,修改后为 2024-01-01 10:01:00"
- 优点:完全保证数据同步的一致性,即使包含非确定性函数也不会出错,是数据恢复和同步的最可靠格式
- 缺点:日志体积大,写入和解析的 IO 开销更高,会增加数据库负担

所以就有了一种折中的方案,指定为mixed,记录的内容是前两者的混合
3、(金选)mixed 格式:混合自适应模式
- 逻辑:MySQL 自动判断 SQL 语句的类型 ------ 若语句是 "确定性的"(如
update T set c=1 where id=2),则用 statement 格式;若语句是 "非确定性的"(如包含now ()、rand ()),则自动切换为 row 格式 - 优点:兼顾 statement 的高性能和 row 的一致性,是折中方案
- 缺点:日志格式不固定,增加了数据恢复和问题排查的复杂度
生产环境中,建议优先设置为
row格式 ------ 虽然牺牲了部分性能,但数据一致性是集群架构的核心要求,且现代硬件的 IO 能力足以承载row格式的开销;仅在非核心、低并发的单机场景下,可考虑mixed或statement格式。
binlog 的写入与刷盘机制
binlog的写入时机也非常简单,事务执行过程中,先把日志写到binlog cache,事务提交的时候,再把binlog cache写到binlog文件中。
因为一个事务的binlog不能被拆开,无论这个事务多大,也要确保一次性写入,所以系统会给每个线程分配一个块内存作为binlog cache。
我们可以通过binlog_cache_size参数控制单个线程 binlog cache 大小,如果存储内容超过了这个参数,就要暂存到磁盘(Swap)避免事务中断。
binlog日志刷盘流程如下:

上图 binlog 的刷盘分为 "write" 和 "fsync" 两步:
- write:将 binlog cache 内容写入 page cache(文件系统缓存),未持久化到磁盘,速度快;
- fsync:将 page cache 中的内容刷盘到物理磁盘,是真正的持久化操作。
刷盘时机:由 sync_binlog 参数控制
上面介绍到,binlog 的刷盘分为 "write" 和 "fsync" 两步
sync_binlog参数决定了 fsync 的触发时机:
- 0(默认):事务提交时仅执行 write,fsync 由操作系统自行决定(通常 30 秒一次),性能最高,但服务器宕机会丢失 page cache 中的所有 binlog;

- 1:事务提交时同时执行 write 和 fsync,binlog 实时持久化,数据最安全,但性能略有损耗;
- N(N>1):事务提交时执行 write,累积 N 个事务后触发一次 fsync,兼顾性能与安全,但宕机会丢失最近 N 个事务的 binlog。

核心业务集群中,建议设置
sync_binlog=1;若存在 IO 瓶颈,可将 N 设置为 100-1000(根据业务容忍度调整),同时配合磁盘阵列、SSD 等硬件优化 IO 性能。
3. 两阶段提交
redo log 与 binlog 的一致性难题
虽然它们都属于持久化的保证,但是侧重点不同
redo log 保障 InnoDB 的崩溃恢复,binlog 保障集群数据同步,但若两者的写入逻辑不一致
在执行更新语句过程,会记录redo log与binlog两块日志,以基本的事务为单位,redo log在事务执行过程中可以不断写入,而binlog只有在提交事务时才写入,所以redo log与binlog的写入时机不一样,严重时会导致 "数据恢复后与集群同步的数据不一致" 的问题

以update T set c=1 where id=2为例:
- 若
redo log已刷盘,但binlog写入时发生异常,则原库重启后通过redo log恢复数据(c=1),但从库同步binlog时因缺失这条记录,数据仍为 c=0,最终主从数据不一致; - 若
binlog已写入,但redo log未刷盘,原库重启后无法恢复该修改(c=0),而从库同步binlog后 c=1,同样导致不一致。


两阶段提交的核心逻辑
为了解决两份日志之间的逻辑一致问题,InnoDB存储引擎使用两阶段提交方案。
原理很简单,将redo log的写入拆成了两个步骤prepare和commit,这就是两阶段提交。
- 执行事务的修改操作,将数据页修改写入
Buffer Pool,同时记录undo log; - 写入
redo log,将redo log标记为prepare阶段; - 写入
binlog,并将binlog刷盘(根据sync_binlog策略); - 再次修改
redo log,将其标记为commit阶段,事务完成。

使用两阶段提交 后,写入binlog时发生异常也不会有影响,因为MySQL根据redo log日志恢复数据时,发现redo log还处于prepare阶段,并且没有对应binlog日志,就会回滚该事务。

再看一个场景,redo log设置commit阶段发生异常,那会不会回滚事务呢?

并不会,MySQL 重启后,读取 redo log 发现事务处于prepare阶段,但能通过事务 ID 找到对应的 binlog,判定事务完整,自动将 redo log 标记为 commit,恢复数据,避免不一致。
两阶段提交的本质是 "将两个独立的日志写入操作绑定为一个原子事务 ",牺牲了极少量的性能(因为饿多一次 redo log 写入),但保证了全局数据一致性,是 MySQL 分布式事务的基础。
4. undo log
undo log 的核心作用
事务的原子性要求 "要么全部执行,要么全部回滚",而 undo log 正是实现回滚的核心:所有事务的修改操作执行前,都会先将 "数据修改前的状态" 记录到 undo log 中(例如,更新操作会记录旧值,插入操作会记录 "删除标记"),且 undo log 会优先于数据持久化到磁盘。
当事务执行过程中出现异常,MySQL 会读取 undo log 中的历史数据,将数据页恢复到修改前的状态,保证未完成的事务不会对数据库造成永久性影响
undo log 与 MVCC 的深度绑定
除了事务回滚,undo log 还是 InnoDB 实现 MVCC(多版本并发控制)的核心依赖。MVCC 的目标是 "读写不阻塞,提高并发性能",其实现依赖三大组件:
- 隐藏字段:每行数据包含DB_TRX_ID(修改该记录的事务 ID)、DB_ROLL_PTR(指向 undo log 的指针);
- Read View:事务启动时生成的 "可见性视图",记录当前活跃的事务 ID;
undo log:存储数据的历史版本,通过DB_ROLL_PTR形成版本链。
当事务读取数据时,InnoDB 通过DB_TRX_ID和 Read View 判断数据是否可见:若不可见,则通过DB_ROLL_PTR遍历 undo log 的版本链,找到事务可见的历史版本。这一机制让不同事务能看到不同版本的数据,避免了读写锁的冲突,是 InnoDB 高并发的关键。
undo log 会随着事务的提交逐步清理(如事务提交后,其生成的 undo log 若不再被其他事务的 MVCC 读取,会被后台线程删除),避免磁盘空间无限膨胀;同时,undo log 也以 "段" 的形式存储在 InnoDB 的表空间中,可通过innodb_undo_tablespaces等参数优化其存储布局