数据库三大日志深度解析:Redo Log、Binlog、Undo Log 如何守护你的数据
📌 今日关键词:Redo Log、Binlog、Undo Log、WAL、两阶段提交、crash-safe
大家好,我是数据库小学妹👋
之前我们学过 MVCC,知道 Undo Log 可以实现数据的"时光回溯"。但很多同学问:“MySQL到底有几种日志?它们各自管什么事?为什么要有这么多日志而不是一个搞定?”
说实话,刚开始我也一头雾水——明明都是记录变更,为什么要搞 Redo Log、Binlog、Undo Log 三个概念?直到我真正理解了它们的分工,才发现这是数据库设计中最精妙的部分之一。
今天用一条 UPDATE 语句走一遍流程,把这三个东西的关系理清楚。
一、三种日志分工
| 日志类型 | 归属 | 作用 | 删除策略 |
|---|---|---|---|
| Redo Log | InnoDB 引擎 | 崩溃恢复(crash-safe) | 循环覆盖 |
| Undo Log | InnoDB 引擎 | 事务回滚 + MVCC | 事务结束后可被 purge |
| Binlog | Server 层 | 主从复制 + 数据恢复 | 手动清理或按策略归档 |
记住一句话:Redo Log 负责"死了能活",Undo Log 负责"错了能悔",Binlog 负责"丢了能找"。
二、Redo Log —— 宕机后靠它恢复数据
什么是 Redo Log?
想象一下这个场景:你刚给某个用户充值了 100 元,UPDATE 语句执行到一半,服务器突然断电了。数据会怎样?
如果不做任何处理,这笔账就丢了——因为内存里的修改还没来得及刷到磁盘上。
Redo Log 的作用就是:即使宕机,已提交的事务也不会丢失。
Redo Log 的核心特点
1. 环形文件结构
redo log000001 → redo log000002 → ... → redo log000010 → (回到开头) redo log000001Redo Log 是固定大小的环形文件组(默认总共 1GB),写到末尾就从头开始覆盖。这种设计保证了日志不会因为长期运行而撑爆磁盘。
2. 物理日志
Redo Log 记录的是"在某个数据页上做了什么修改",比如:
[LSN=100] 在 page_id=100 的 offset=4096 处写入 value=100 元这种物理级别的记录让恢复速度非常快——直接按日志重放就行,不用重新解析 SQL。
3. WAL 技术(Write-Ahead Logging)
核心原则:任何修改必须先写 Redo Log,才能认为事务成功。
正常流程(有风险): 修改内存 buffer pool → 异步刷盘 → 告诉用户"成功了" ↑ 如果这里宕机,数据就丢了 WAL 流程(安全): 写 Redo Log 并刷盘 → 修改内存 buffer pool → 告诉用户"成功了" ↑ 即使宕机,重启后通过 Redo Log 能恢复查看 Redo Log 配置
-- 查看 Redo Log 大小和数量SHOWVARIABLESLIKE'innodb_log_file_size';-- 单个文件 256MSHOWVARIABLESLIKE'innodb_log_files_in_group';-- 4 个文件,共 1GB-- 查看 checkpoint 位置(决定了能回收多少 log)SHOWENGINEINNODBSTATUS\G三、Undo Log —— 回滚和版本链
Undo Log 我们之前学 MVCC 时提过,今天从更底层角度再拆解一遍。
Undo Log 的两个职责
职责 1:事务回滚
你执行了一个错误操作,比如:
BEGIN;UPDATEaccountsSETbalance=0WHEREuser_id=123;-- 哎呀手滑了!ROLLBACK;数据库怎么知道你原来的余额是多少?靠的就是 Undo Log 里保存的"旧值"。
Undo Log 记录的格式类似这样:
[事务 A] 将 accounts 表中 user_id=123 的 balance 从 500 改为 0 ↓ 反向操作就是 [回滚] 将 accounts 表中 user_id=123 的 balance 从 0 改回 500职责 2:MVCC 版本链
同一个表的同一行数据,不同事务看到的版本可能不一样:
时间线: T1: 事务 A 把 balance 从 100 改成 200(undo log 指向旧版本) T2: 事务 B 把 balance 从 200 改成 300(undo log 指向 T1 的版本) T3: 事务 C 查询,如果是 RC 级别看到 300,如果是 RR 级别且早于 T1 则看到 100每一行数据的 undo log 连成一条"版本链",越新的数据在链表头部,越旧的在后面。
Undo Log vs Redo Log
| 特性 | Undo Log | Redo Log |
|---|---|---|
| 存储位置 | 表空间内(系统表空间或独立 undo 表空间) | 独立的 redo log 文件 |
| 是否循环使用 | 否,事务结束后被 purge 线程清理 | 是,checkpoint 后可覆盖 |
| 记录内容 | 逻辑日志(反向日志) | 物理日志(数据页修改) |
| 主要用途 | 回滚 + MVCC | 崩溃恢复 |
四、Binlog —— Server 层的日志文件
Binlog 是什么?
Binlog(Binary Log)是 MySQL Server 层的日志,所有引擎(InnoDB、MyISAM 等)都会产生。
它记录的是 SQL 语句的逻辑变更,比如:
# At 123456789 #260521 10:30:00 server id 1 end_log_pos 123456789 Query SET timestamp=1684658400; UPDATE accounts SET balance = 100 WHERE id = 1Binlog 的三种格式
-- 查看当前 binlog 格式SHOWVARIABLESLIKE'binlog_format';| 格式 | 记录内容 | 优点 | 缺点 |
|---|---|---|---|
| STATEMENT | 原始 SQL 语句 | 体积小 | 某些函数(如 NOW())主从不一致 |
| ROW | 每行变更的前后镜像 | 精确可靠 | DDL 或批量更新时体积大 |
| MIXED | 混合模式,自动切换 | 平衡两者 | 逻辑复杂 |
生产环境推荐用 ROW 模式,虽然体积大点,但最安全。
Binlog 的独特价值
1. 数据恢复(Point-in-Time Recovery)
误删了一张表?可以通过 Binlog 恢复到任意时间点:
# 停止 MySQL# 恢复全量备份# 回放 Binlog 到删除前的时间点mysqlbinlog --stop-datetime="2026-05-21 10:29:59"binlog.000001|mysql-uroot-p2. 主从复制
从库通过读取主库的 Binlog,保持数据同步——这是我们之前学主从复制时的基础。
Binlog 管理命令
-- 查看 Binlog 列表SHOWBINARYLOGS;-- 清空过期 Binlog(谨慎操作!)PURGEBINARYLOGS BEFORE'2026-05-01 00:00:00';-- 强制刷新 Binlog 到新文件FLUSH LOGS;-- 关闭/开启 Binlog(一般不用)SETGLOBALlog_bin=0;五、一条 UPDATE 语句的执行流程
现在看执行一条 UPDATE 时,三个日志怎么协作:
UPDATEaccountsSETbalance=balance+100WHEREuser_id=1;执行流程
┌─────────────────────────────────────────────────────────────┐ │ 1. 检查 Buffer Pool,找到对应的数据页 │ │ ↓ │ │ 2. 生成 Undo Log(记录旧值 balance=500) │ │ ↓ │ │ 3. 修改内存中的数据页(balance 变成 600) │ │ ↓ │ │ 4. 写 Redo Log(记录"page X 的 offset Y 修改为 600") │ │ ↓ │ │ 5. 写 Binlog(记录"UPDATE accounts...") │ │ ↓ │ │ 6. 两阶段提交:先准备(prepare),再提交(commit) │ │ ↓ │ │ 7. 返回给客户端:"Query OK, 1 row affected" │ └─────────────────────────────────────────────────────────────┘关键点:两阶段提交
为什么不写完 Redo Log 和 Binlog 就直接提交,而要分两个阶段?
场景假设:
- 写完 Redo Log 并提交,然后宕机
- 结果:Redo Log 里有这条记录,但 Binlog 没有
- 后果:主库恢复了,但从库没有这条变更记录→数据不一致!
两阶段提交的解决方案:
阶段 1(Prepare): 写 Redo Log,标记为"prepare"状态 ↓ 阶段 2(Commit): 写 Binlog 并刷盘 写 Redo Log 的 commit 标记 ↓ 事务完成如果中途宕机,重启后通过检查 Redo Log 的状态来决定是否恢复:
- 只有 prepare 标记 → 不恢复(说明 Binlog 没写完)
- 有 commit 标记 → 恢复
六、实战:监控与调优
1. 检查 Redo Log 使用情况
-- 查看 checkpoint 进度SHOWENGINEINNODBSTATUS\G# 搜索 "LOG" 部分,关注:# - Log sequence number(当前 LSN)# - Checkpoint age(距离 checkpoint 还有多远)Checkpoint age 过大说明刷盘跟不上写入速度,可以考虑:
- 增大 innodb_log_file_size
- 优化写入负载
2. Undo Log 膨胀排查
长事务会导致 Undo Log 无法被清理:
-- 查找长事务SELECTtrx.trx_id,trx.trx_state,trx.trx_started,TIMESTAMPDIFF(SECOND,trx.trx_started,NOW())asrunning_secondsFROMinformation_schema.innodb_trx trxWHERETIMESTAMPDIFF(SECOND,trx.trx_started,NOW())>60;发现有长事务要尽快定位原因,通常是因为业务代码里事务包裹的范围太大。
3. Binlog 大小监控
-- 查看所有 binlog 总大小SELECTFILE_NAMEaslog_name,FILE_LENGTHassize_bytes,ROUND(FILE_LENGTH/1024/1024,2)assize_mbFROMinformation_schema.FILESWHEREFILE_TYPE='REDO LOG'ORFILE_NAMELIKE'%binlog%';-- 或者直接从系统表查SHOWBINARYLOGS;Binlog 占用太大时可以调整保留策略:
# my.cnf [mysqld] expire_logs_days = 7 # 7 天后自动删除 max_binlog_size = 100M # 单个 binlog 最大 100M七、常见问题速答
Q1: Redo Log 和 Binlog 的区别?
A: Redo Log 是 InnoDB 引擎层的物理日志,用于崩溃恢复,循环使用;Binlog 是 Server 层的逻辑日志,用于主从复制和数据恢复,不会自动覆盖。
Q2: 为什么要两阶段提交?
A: 保证 Redo Log 和 Binlog 的一致性。如果一个事务在 Redo Log 中提交了但 Binlog 没写完,单阶段提交会导致主从数据不一致。
Q3: Undo Log 什么时候被清除?
A: 当事务不需要再读旧版本时,Purge 线程会在后台清理 Undo Log。具体来说,RR 级别下事务结束后可清理,RC 级别下每条 SELECT 后可清理不再需要的旧版本。
Q4: Redo Log 满了怎么办?
A: Redo Log 是环形文件,满了就从头开始覆盖。但有个前提:_checkpoint_必须已经追赶上来,即对应的脏页已经刷回磁盘。如果写入速度太快导致 checkpoint 追不上,MySQL 会挂起写入直到有空间。
八、今日学习心得
- Redo Log 是 InnoDB 的"保险箱",确保宕机后数据不丢失
- Undo Log 是事务的"后悔药",同时支撑 MVCC 的版本链
- Binlog 是 Server 层的"审计日志",用于主从复制和时间点恢复
- 两阶段提交是保证 Redo Log 和 Binlog 一致性的关键
- 理解这三种日志,才能真正掌握 MySQL 的事务机制
👋 我是数据库小学妹一个用设计师思维学数据库的转行人。我们一起,把复杂的技术变得简单有趣!💕
本文示例基于MySQL8.0 + InnoDB。
