主题
概念卡片:MySQL 日志与两阶段提交
一句话机制
MySQL 更新靠 WAL(先写日志再写磁盘):InnoDB 的 redo log(物理日志,崩溃恢复)+ Server 层的 binlog(逻辑日志,归档/主从),两者用「两阶段提交」保证逻辑一致。 这是「数据库异常重启数据不丢」和「主从一致」的根基。
三大日志对比
| 日志 | 归属 | 内容 | 写入 | 作用 |
|---|---|---|---|---|
| redo log | InnoDB 特有 | 物理日志(某数据页改了什么) | 循环写(固定空间,写满擦除) | crash-safe 崩溃恢复 |
| binlog | Server 层(所有引擎) | 逻辑日志(语句原始逻辑) | 追加写(写满换文件) | 归档/主从复制/数据恢复 |
| undo log | InnoDB | 记录回滚操作 | — | 事务回滚 + MVCC 多版本 |
redo log 的循环写
write pos(当前写位置)→ 一直后移
checkpoint(当前擦除位置)→ 擦除前先刷盘
两者之间 = 可写空间;write pos 追上 checkpoint = 写满,需先推进 checkpointWAL(Write-Ahead Logging)
先写日志、再写磁盘:更新时先把记录写 redo log + 更新内存,后台空闲时再刷数据页。把「随机写数据页」变成「顺序写日志」,性能大幅提升。
两阶段提交(为什么必须)
一条 UPDATE 的完整流程:
1. 执行器取数据(内存有直接用,否则读盘)
2. 执行器改值,调引擎写接口
3. InnoDB:更新内存 + 写 redo log(状态 = prepare) ← 阶段1
4. 执行器:写 binlog 到磁盘
5. InnoDB:redo log 状态改 commit ← 阶段2为什么不用「先写 redo 再写 binlog」或反过来? 都会导致崩溃后「库的状态」和「日志恢复出来的库」不一致:
- 先写 redo 后写 binlog:redo 写完 binlog 没写就 crash → 库里 c=1(redo 恢复了),但 binlog 没记录,用 binlog 恢复的临时库 c=0 → 不一致
- 先写 binlog 后写 redo:binlog 写完 redo 没写就 crash → 库 c=0(事务无效),但 binlog 记录了,恢复库 c=1 → 不一致
两阶段提交保证 redo log 和 binlog 逻辑一致,主从才不会漂移。
保证不丢数据的两个参数
| 参数 | 设为 1 的含义 |
|---|---|
innodb_flush_log_at_trx_commit=1 | 每次事务 redo log 直接刷盘 |
sync_binlog=1 | 每次事务 binlog 直接刷盘 |
两个都设 1,异常重启数据不丢(代价是每次事务两次刷盘,性能略降)。
常见误解(避坑)
- ❌ "redo log 和 binlog 可以只用一个"。→ 一个管崩溃恢复(引擎)、一个管归档主从(Server),职责不同,缺一不可。
- ❌ "redo log 能无限写"。→ 循环写、空间固定,写满要擦除(推进 checkpoint)。
- ❌ "两阶段提交是为了性能"。→ 是为了一致性(redo/binlog 逻辑一致),性能是 WAL 带来的。
- ❌ "binlog 是物理日志"。→ binlog 是逻辑日志(记语句逻辑),redo log 才是物理日志。
关联
- 原始资料:02_日志系统:一条SQL更新语句是如何执行的? · 6.1 核心日志-bin log · 6.2 核心日志-redo log · 6.3 核心日志-undo log
- 总览:MySQL技术栈总览
- 相关卡:概念卡片:MySQL架构与SQL执行流程(redo 引擎层/binlog Server 层的分层根源)
- 域地图:A00-百科/数据与存储/数据与存储