如果不熟悉 redolog、undolog,可以先看第三部分的相关介绍。
一、事务基本过程
1、修改内存页(在 Buffer Pool 里完成)。
2、把对应的 redo log 记录写到 redo log buffer(内存)。
3、事务提交时,redo log buffer 先刷盘(顺序写 redo log 文件)。
3.1、如果开启 binlog,redo、binlog 2PC 提交,保证原子性。
4、然后再把脏页(16 KB)写到 doublewrite buffer(磁盘 2 MB 区)。
5、最后才把同一份 16 KB 页写到真正的数据文件。
所以 redo log 先于 doublewrite buffer 落盘,但 redo log 本身不携带整页数据,只记录“增量”。redo log 管逻辑,doublewrite buffer 管物理完整。
二、整体过程
1、语句执行阶段
-> 先在 Buffer Pool 修改内存、生成 undo、生成 redo 记录(仍然只在内存)。
2、收到客户端 COMMIT
-> 立即把该事务的 redo log buffer 刷盘(fsync)
-> 成功后向客户端返回「Query OK」
3、之后
-> 脏页异步刷盘(内存脏页 -> doublewrite,同一份内存脏页 -> 数据文件)
注意:上面是 innodb_flush_log_at_trx_commit = 1 的情形,如果是 0、2 的话,第 1 步就 OK 了。
三、undo log、redo log、doublewrite buffer
redo log 包含「数据页」和「undo页」的变更。
1、生成阶段
- 修改数据页 → 生成 数据页 的 redo 记录 
- 同时插入 undo 记录 → 生成 undo 页 的 redo 记录 
这两类 redo 记录混在同一条 redo log buffer 里。每条 redo 自带长度字段,fsync 只会刷“连续且完整”的 redo 记录。也就是这两部分写入具有原子性。
2、刷盘阶段
redo log 刷盘(fsync)只按 LSN 顺序一次性把 buffer 里的所有记录(数据+undo)写盘。
「数据页」和「undo 页」本身仍作为普通脏页(16K),稍后由后台线程异步刷盘。
3、redo log
格式示例:(type=MLOG_REC_UPDATE, space_id=6, page_no=12345, offset=0x3A0, old_bytes=0x11, new_bytes=0x22)
作用:记录物理页上某个偏移量的字节如何变化(增量)
redo log 重放日志,ib_logfile0、1(固定 2 个文件,循环写)
需要依赖「完整页」才能重放,需要 doublewrite buffer 提供完整的页镜像。
4、undo log
一般称为回滚日志、撤销日志,MySQL 5.7 默认把 undo log 放在系统表空间 ibdata1 里。
格式示例:(trx_id=1001, roll_ptr=0x00…7F, op=UPDATE, table_id=18, old_row=<完整旧行>)
作用:记录逻辑旧行整行内容,用于回滚或 MVCC 读取旧版本。
undo 页刷盘只是“把内存里的垃圾搬到磁盘”,回滚的正确性由 redo log 保证。一条事务 commit 后,undo log 仍然保留一段时间,因为:
(1)MVCC 需要
其他事务可能正在用你的旧版本数据做一致性读;
只有所有活跃事务都不再引用这些旧版本时,undo 记录才能被 purge。
(2)purge 线程异步清理
当旧版本无人引用,purge 线程把对应的 undo 记录标记为可重用,并最终把 undo 页刷盘(写覆盖,空间回收)
5、doublewrite buffer
双写缓冲,位于系统表空间 ibdata1 内部,固定偏移量(2 MB 连续区域),默认路径与 datadir 一致。
保存脏页的完整 16 KB 物理数据(整页内容),用于崩溃修复“半页损坏”。
6、总结
- redolog 记录的是增量变化(物理页偏移字节),即原始数据页(16K)+ redolog = 变更后数据页(脏页 16K) 
- doulewrite buffer 就是 16K 的脏页(备份) 
- 脏页落盘 —— 编辑原始数据页(16K),脏页覆写。 
脏页落盘过程中 Mysql 异常,会导致数据页损坏(半页损坏)。Mysql 恢复后可以用 doulewrite buffer 重复脏页落盘的过程,redolog 是针对完整的原始数据页的,此时数据页损坏,redolog 对恢复也就没有帮助了。
四、redo log 落盘怎么保证原子性?
1、记录完整性
每条 redo 记录自带 长度字段,写完才更新尾部指针;fsync 只刷到「完整记录」边界,不会半条落盘。
保证写的那一刻是完整的。
2、顺序追加写
redo log 是固定环形文件,顺序追加无寻道;磁盘控制器通常能保证 512B / 4KB 原子写。
消费级 SSD 都有掉电保护电容,保证 4KB 原子写;在支持 16KB 原子写的企业级别 SSD 上,可以关闭 Doublewrite Buffer,仍能保证页级完整性(InnoDB 16KB 页)。
3、崩溃恢复校验
重启时按 LSN 扫描,读到不完整记录直接丢弃,保证不会应用损坏的 redo。损坏部分可以视为未提交。
五、事务等待 redo log 刷盘会慢吗?
也就是 innodb_flush_log_at_trx_commit = 1,不会「慢到不可接受」,原因有三:
1、顺序写 + 追加写
redo log 是固定大小的环形文件,每次只把几十字节 ~ 几 KB 的记录顺序追加到最后,无磁盘寻道,现代 SSD 顺序写延迟 < 100 μs。
2、组提交(Group Commit)
多个并发事务的 redo log 在一次 fsync 中合并刷盘,平均到每事务的代价进一步摊平。MySQL 5.7默认开启。
流程图(简化):
# 同一毫秒内到来的多个事务共享一次 fsync
┌──事务A commit──┐
└─加入队列 LSN=100
                 ┌──事务B commit──┐
                 └─加入队列 LSN=102
                                 ▼
                     log_flusher 线程
                     fsync(LSN≤102) 一次
                                 ▲
                 ┌──事务A返回
                 └──事务B返回3、参数可控
- innodb_flush_log_at_trx_commit = 1 (默认)保证每次 commit 立即 fsync,延迟约 0.1 ~ 0.5 ms;支付链路永远 1,不能因为量少就冒险。这个参数决定 redo log 的刷盘时机。 
- 若能接受 1s 丢数据,可设为 2,延迟降到微秒级(非核心业务/日志库)。 
因此,即使高并发,单条事务 commit 的刷盘延迟通常 < 1 ms,不会成为瓶颈。
六、redo、binlog 落盘策略
1、binlog 刷盘策略 sync_binlog
- N 表示 每 N 个事务或每 N 次 write 后执行一次 fsync. 
- 1 表示每次事务提交立即刷盘(5.7 默认。最安全,也最慢) 
- 0 表示由操作系统决定何时刷盘(最快,有丢失风险) 
2、2PC 刷盘过程
开启 binlog 日志之后, redo 刷盘变为两端提交(2PC)。
设置 sync_binlog = 1、innodb_flush_log_at_trx_commit=1 (双1),才能保证刷盘的原子性。
具体过程:
(1)prepare
先写 redo log,事务状态标为 prepare。
(2)write / flush binlog
再把 binlog 写到 binlog 文件(拷进内核 PageCache)并 fsync(落盘)
(3)commit
redo log 里把事务状态改为 commit。
崩溃恢复时,InnoDB 按 binlog 是否存在且完整来判断:
- redo 已 prepare、binlog 存在且完整 -> 提交 
- redo 已 prepare、binlog 不存在或不完整 -> 回滚。 
注意:开启 binlog,一般要做数据恢复或者从库同步。如果不用「双1」,不能保证 redo、binlog 顺序,可能出现「binlog 有,redo 无」或者相反结果。
3、redo、binlog 刷盘参数(安全性从高到低)
(1)双 1(金融、支付、银行系统)
一般用于生产主库,保证数据一致性,支持 Crash-Safe。
(2)1,N
innodb_flush_log_at_trx_commit=1 、sync_binlog = 100 或 1000
适合大多数生产环境配置,电商、用户中心、高并发写入系统。
性能安全折中,适合写入频繁但可容忍少量事务丢失的场景。
(3)1,0
高性能,不使用主从复制或接受复制断裂风险。
binlog 依赖 OS 刷盘,主机崩溃可能丢失大量数据,进而复制断裂,要重做从库。
(3)2,N
- 安全性:2,1 > 2,N(100 ~ 1000) > 2,0 
- 性能高,2,N 相比 2,0 数据丢失风险可控,主从不一致风险低 
- 适合需要一定性能优化,但又不能完全容忍数据丢失的场景(如报表系统) 
(4)innodb_flush_log_at_trx_commit≠1、syn_binlog = 1
此时可能会出现 binlog 超前(错位),过程如下:
主库发现没有 redo -> 事务视为未提交,数据还是旧值;从库收到 binlog -> 数据变成新值。
结果:从库比主库多一条记录。如果是日志库,永久容忍这条差异没问题;如果是关键业务库可能要逆向 SQL 恢复、重做从库或者使用 pt-table-sync 把差异补齐。