MySQL三大日志(binlog、redo log和undo log)
MySQL三大日志(binlog、redo log和undo log)
MySQL 日志 主要包括错误日志、查询日志、慢查询日志、事务日志、二进制日志几大类。其中,比较重要的还要属二进制日志binlog(归档日志)和事务日志redo log(重做日志)和undo log(回滚日志)。
redo log(重做日志)
redo log是物理日志,他记录的是某个数据页做了什么修改,属于InnoDB存储引擎内部的日志,主要用于保证不丢数据(持久性)。
具体流程:
- MySQL以页为单位从磁盘中读取数据,加载出来的数据叫数据页,会放入到
Buffer Pool中。 - 后续查询都是先从
Buffer Pool中读取数据,如果数据页不在Buffer Pool中才会从磁盘中读取。 - 更新表数据时也是如此,发现
Buffer Pool里存在要更新的数据,就直接在Buffer Pool里更新。 - 然后会把“在某个数据页上做了什么修改”记录到重做日志缓存(redo log buffer)中,接着刷盘到redo log文件中。

刷盘时机
刷盘时机指的是redo log buffer中的日志什么时候刷盘到磁盘中,MySQL提供了三种刷盘方式:
- 事务提交时(最核心):当事务提交时,MySQL会将redo log buffer中的日志刷盘到磁盘中,以确保事务的持久性。可以通过
innodb_flush_log_at_trx_commit参数来控制刷盘的行为。 - redo log buffer空间不足时:
- 当redo log buffer已用空间达到50%时,后台线程会主动将这部分日志刷新到磁盘中。
- 如果因为大事务或IO繁忙导致buffer被写满,那么所有试图写入redo log buffer的线程都会被阻塞,并强制进行一次同步刷盘。
- 触发检查点(Checkpoint)时:Checkpoint是InnoDB为了缩短崩溃恢复时间而设计的核心机制。
- Checkpoint 是一个“安全位置标记”,表示在这个位置之前的 redo log 已经不再需要用于崩溃恢复。换句话说:Checkpoint之前的修改都已经真正写入了数据文件(.ibd)。Checkpoint之后的日志,才是恢复时需要重放的。
- Checkpoint是InnoDB中用于标记redo log安全位置的机制。表示在该位置之前的日志对应的数据页已经刷盘,因此不再需要用于崩溃恢复。它的作用是保证 redo log 可以循环覆盖,同时避免日志空间耗尽。
- 后台线程周期性刷新:InnoDB后台的master thread大约每秒执行一次例行任务,其中就包括将redo log buffer中的日志刷新到磁盘。这个机制是
innodb_flush_log_at_trx_commit设置为0或2时的主要持久化保障。 - 正常关闭服务器:当MySQL服务器正常关闭时,会将redo log buffer中的日志刷盘到磁盘中,以确保数据的完整性。
- binlog切换时:当开启binlog后,在MySQL采用
innodb_flush_log_at_trx_commit=1和sync_binlog=1的双一配置下,为了保证redo log和binlog之间状态的一致性(用于崩溃恢复或主从复制),在binlog文件写满或者手动执行flush logs进行切换时,会触发redo log的刷盘动作。
innodb_flush_log_at_trx_commit参数
innodb_flush_log_at_trx_commit参数控制着redo log的刷盘行为,主要有以下三个取值:
- 值为0:表示事务提交时不进行刷盘。
- 值为1:表示事务提交时进行刷盘(默认值)。
- 值为2:表示事务提交时都只把log buffer里的redo log内容写入page cache(文件系统缓存)。page cache是专门用来缓存文件的,这里被缓存的文件就是redo log文件。
另外,InnoDB后台线程每隔1秒就会把redo log buffer中的内容写到page cache,然后调用fsync刷盘。

日志文件组
InnoDB的redo log文件是由多个日志文件组成的,称为日志文件组(log file group)。
每个日志文件的大小由innodb_log_file_size参数控制。日志文件组中的日志文件数量由innodb_log_files_in_group参数控制。
MySQL8.0.30及之后的版本中这两个参数已经被废弃,即使指定也是用来计算innodb_redo_log_capacity的。日志文件组的文件数固定为32,文件大小则为innodb_redo_log_capacity/32。
binlog(归档日志)
binlog是MySQL Server层的日志,不属于某个存储引擎。它记录的是逻辑操作。
比如:
1 | update user set age = 30 where id = 1; |
binlog记录的是SQL或行变更信息,而不是物理页修改。
它有两个核心作用:
- 主从复制:从库就是读取主库的binlog,然后重放。
- 数据恢复:比如误删数据,可以用binlog做
point-in-time recovery(基于时间点恢复)。
redo log 是用来“崩溃恢复”,
binlog 是用来“复制和归档”。
binlog格式
binlog有三种格式,可以使用binlog_format参数进行设置:
- statement:记录SQL语句的文本形式。
- row:记录行级别的变更信息,包含了被修改的行的具体数据。
- mixed:MySQL会根据具体情况自动选择使用statement或row格式。
它们的区别核心只有一个问题:记录“SQL语句”还是记录“数据变化”?
statement(基于语句)
记录的是原始 SQL。
比如:
1 | update user set age = 30 where id = 1; |
binlog 里记录的就是这条 SQL。
优点:
- 日志体积小
- 写入快
问题:
- 如果SQL执行结果依赖环境,主从可能不一致
典型例子:
1 | update user set create_time = now(); |
主库执行时间和从库执行时间不同。
或者:
1 | update user set id = id + 1 limit 1; |
如果没有明确order by,主从选中的行可能不同。
statement本质风险是:依赖确定性。
row(基于行)
不记录SQL,而是记录“数据行的变化”。
比如刚才的 update:
binlog 会记录:
- 修改前的行数据
- 修改后的行数据
主库改哪一行,从库就精确改哪一行。
优点:
- 最安全
- 不会因函数、时间、limit 等产生不一致
缺点:
- 日志体积大
- 大批量更新会产生大量 binlog
这是当前默认推荐格式。
mixed(混合模式)
MySQL自动判断:
- 能安全用statement就用statement
- 可能不安全就用row
它试图在性能和安全之间折中。
但在生产中:
大多数系统直接使用row。
因为:
复制一致性 > 日志体积。
对比
statement:记录“怎么做”
row:记录“做了什么”
mixed:自动选择
写入机制
事务执行时,先把日志写到binlog cache,事务提交时再把binlog cache中的日志写入磁盘上的binlog文件。
一个事物的binlog不能被拆开,所以系统会给每个线程分配一个块内存作为binlog cache,可以通过binlog_cache_size参数设置这个缓存的大小。
binlog日志刷盘流程如下:

- 上图的
write,是指把日志写入到文件系统的page cache,并没有把数据持久化到磁盘,所以速度比较快 - 上图的
fsync,才是将数据持久化到磁盘的操作 write和fsync的时机,可以由参数sync_binlog控制,默认是1。- 为0的时候,表示每次提交事务都只write,由系统自行判断什么时候执行fsync。
- 为1的时候,表示每次提交事务都要执行一次fsync。
- 还可以设置为N(N>1),表示每次提交事务都
write,但每N次提交才执行一次fsync。
两阶段提交
redo log(重做日志)让InnODB存储引擎有了崩溃恢复能力。
binlog(归档日志)保证了MySQL集群架构的数据一致性。
redo log在事务执行过程中可以不断写入,但binlog只有在事务提交时才写入。

redo log和binlog分属两个系统层级,如何保证它们的一致性?
redo log 属于 InnoDB(存储引擎层,物理日志)
binlog 属于 Server 层(逻辑日志)
如果只成功写了其中一个,就会产生灾难。
为什么必须两阶段?
设想两个失败场景。
场景 1:
- redo log 写成功
- binlog 写失败
- 此时崩溃
重启后:
InnoDB 根据 redo log 恢复数据
但 binlog 没有记录这次修改
如果做主从复制,从库永远不知道这次修改。
主从数据不一致。
场景 2:
- binlog 写成功
- redo log 写失败
- 崩溃
主库恢复后没有这条数据
但从库会执行 binlog
再次不一致。
所以必须保证:
- 要么两个都成功
- 要么两个都失败
MySQL 中的两阶段流程
不是抽象协议,而是具体执行顺序。
第一阶段:prepare 阶段
- InnoDB 写 redo log
- 将 redo log 标记为 prepare 状态
- 刷盘(保证持久)
此时事务还未真正提交。
第二阶段:commit 阶段
- 写 binlog
- binlog 刷盘
- 通知 InnoDB 将 redo log 标记为 commit
至此事务完成。

崩溃恢复时怎么判断?
重启时 InnoDB 会检查 redo log 中的状态:
如果是 commit 状态 → 直接提交
如果是 prepare 状态 → 检查 binlog
- binlog 中有这条事务 → 提交
- binlog 中没有 → 回滚
通过这个机制实现最终一致。
面试标准回答
MySQL 为了保证redo log和binlog的一致性,采用两阶段提交机制。
第一阶段:InnoDB写redo log并标记为prepare。
第二阶段:写binlog并刷盘,然后将redo log标记为commit。
崩溃恢复时,如果redo log处于prepare状态,会通过binlog判断事务是否真正提交,从而保证两种日志的一致性。
undo log(回滚日志)
undo log —— 保证可回滚 + MVCC
undo log也是InnoDB的日志,但它是逻辑日志,记录的是修改前的值。
两个作用:
第一,事务回滚
比如:
1 | update user set age = 30 where id = 1; |
如果执行一半你rollback了怎么办?
undo log里记录了age修改前的值,比如25。回滚时就把25写回去。
第二,MVCC(多版本并发控制)
在REPEATABLE READ隔离级别下,一个事务可以读到“历史版本”的数据。这个历史版本就是通过undo log形成的版本链。
每次修改都会生成一个undo版本,串成链表。
查询时通过Read View判断可见性,决定读哪个版本。
redo log和undo log的区别
undo log 解决的是“事务自己后悔”
redo log 解决的是“系统突然死亡”
两者关注的不是同一个风险。
时间维度不同
undo log 面向的是:事务还活着,但决定撤销。
redo log 面向的是:数据库进程已经崩溃,需要恢复到一致状态。
一个是主动回退。
一个是被动重建。
数据方向不同
undo log 记录的是“旧值”(before image)。
它让数据可以往回走。
比如:
age: 20 → 30
undo 里保存 20。
回滚时把 20 写回去。
这是时间倒流。
redo log 记录的是“修改操作”。
它让数据可以往前补。
比如:
age 从 20 改成 30
redo 记录的是这次物理变更。
如果崩溃时数据页还没落盘,
重启时通过 redo 把修改重新做一遍。
这是时间补偿。
触发场景不同
undo log 使用场景:
- 事务 rollback
- MVCC 读历史版本
redo log 使用场景:
- 宕机后 crash recovery
- WAL 保证持久性
undo 是逻辑一致性工具。
redo 是持久性保障工具。
为什么两个都需要?
假设只有 undo,没有 redo。
事务提交成功,数据在内存,
还没刷盘就宕机。
重启后,数据丢了。
undo 解决不了。
假设只有 redo,没有 undo。
事务执行到一半要回滚,
怎么办?
redo 只记录“怎么改”,
没有旧值,回不去。
数据库必须同时具备:
- 向后退的能力(undo)
- 向前补的能力(redo)
总结
MySQL InnoDB引擎使用redo log(重做日志)保证事务的持久性,使用undo log(回滚日志)来保证事务的原子性。
MySQL数据库的数据备份、主备、主主、主从都离不开binlog,需要依靠binlog来同步数据,保证数据一致性。
