一句话总结
MySQL InnoDB 有三大日志:redo log(重做日志)— 物理日志,记录"数据页做了什么修改",用于崩溃恢复,实现 WAL 机制;undo log(回滚日志)— 逻辑日志,记录"修改前的数据",用于事务回滚和 MVCC;binlog(归档日志)— Server 层逻辑日志,记录所有 SQL 语句,用于主从复制和数据恢复。redo log 和 binlog 通过两阶段提交保证一致性。
初级理解
三大日志对比
| 对比维度 | redo log | undo log | binlog |
| 所属层 | InnoDB 引擎层 | InnoDB 引擎层 | MySQL Server 层 |
| 日志类型 | 物理日志 | 逻辑日志 | 逻辑日志 |
| 记录内容 | 数据页的物理修改 | 修改前的数据(回滚用) | SQL 语句或行变更 |
| 写入方式 | 循环写(固定大小) | 随机写(追加) | 追加写 |
| 主要作用 | 崩溃恢复(crash-safe) | 事务回滚 + MVCC | 主从复制 + 数据恢复 |
| 可否关闭 | 不可关闭 | 不可关闭 | 可关闭(不推荐) |
一句话总结:redo log 保持久(崩溃恢复),undo log 保原子(回滚),binlog 保复制(主从同步)。
中级深入
redo log — WAL 机制与崩溃恢复
WAL(Write-Ahead Logging):先写日志,再写磁盘。事务提交时,先写 redo log,再异步刷脏页到磁盘。这样即使刷脏页前宕机,重启后也能通过 redo log 恢复数据。
# redo log 配置
SHOW VARIABLES LIKE 'innodb_log%';
# innodb_log_file_size: 单个 redo log 文件大小(默认 48M)
# innodb_log_files_in_group: redo log 文件数量(默认 2)
# innodb_log_buffer_size: redo log 缓冲区大小
# redo log 写入策略(innodb_flush_log_at_trx_commit)
# 0: 每秒刷一次(性能最好,可能丢 1 秒数据)
# 1: 每次提交都刷盘(默认,最安全)
# 2: 每次提交写 OS 缓存,每秒刷盘(折中)
# 循环写示意
# redo log 是固定大小的环形结构
# write pos: 当前写入位置
# checkpoint: 已刷盘位置
# write pos 追上 checkpoint → 需要等待刷盘(可能阻塞)
binlog — 主从复制与数据恢复
# binlog 配置
SHOW VARIABLES LIKE 'log_bin%';
SHOW VARIABLES LIKE 'binlog%';
# sync_binlog: binlog 刷盘策略
# 0: 由 OS 决定何时刷盘
# 1: 每次提交都刷盘(推荐,最安全)
# N: 每 N 次提交刷一次盘
# 数据恢复示例
# 1. 全量备份 + binlog 恢复到指定时间点
mysqlbinlog --start-datetime="2025-01-01 00:00:00" \
--stop-datetime="2025-01-01 12:00:00" \
mysql-bin.000001 | mysql -u root -p
# 2. 恢复到指定位置
mysqlbinlog --start-position=1234 --stop-position=5678 \
mysql-bin.000001 | mysql -u root -p
两阶段提交 — redo log 与 binlog 的一致性
为什么需要两阶段提交?因为 redo log 和 binlog 是两个独立的系统,如果写了一个另一个没写,会导致主从不一致。
# 两阶段提交流程
# 1. Prepare 阶段:写 redo log,标记为 prepare 状态
# 2. 写 binlog
# 3. Commit 阶段:写 redo log,标记为 commit 状态
# 崩溃恢复判断:
# - redo log 中有 commit 标记 → 事务已提交,直接恢复
# - redo log 中有 prepare 标记,binlog 中有对应记录 → 提交
# - redo log 中有 prepare 标记,binlog 中无对应记录 → 回滚
# 为什么先写 redo log 再写 binlog?
# 如果先写 binlog 再写 redo log:
# binlog 写了但 redo log 没写 → 崩溃后从库有数据,主库没有 → 不一致
中级要点:redo log 循环写保证崩溃恢复;binlog 追加写保证复制;两阶段提交保证两者一致。
高级拓展
一条 UPDATE 语句的执行流程(含日志)
# UPDATE users SET name='李四' WHERE id=1;
# 1. 执行器调用 InnoDB 引擎接口,读取 id=1 的行
# 2. InnoDB 从 Buffer Pool 中查找,没有则从磁盘加载
# 3. 执行器修改 name='李四',调用引擎写入接口
# 4. InnoDB 将修改前的数据写入 undo log(用于回滚和 MVCC)
# 5. InnoDB 修改 Buffer Pool 中的数据页(脏页)
# 6. InnoDB 将修改操作写入 redo log buffer
# 7. 执行器写 binlog(binlog cache → binlog 文件)
# 8. 提交事务:
# a. redo log prepare
# b. 写 binlog
# c. redo log commit
# 9. 返回客户端"更新成功"
# 10. 后台线程异步将脏页刷入磁盘
redo log 块满了怎么办?
redo log 是循环写的固定大小文件。当 write pos 追上 checkpoint 时,需要等待 checkpoint 推进(将脏页刷盘),此时所有写操作被阻塞。这就是为什么 redo log 不能太小——太小会导致频繁刷盘,影响性能。
# 查看 redo log 使用情况
SHOW ENGINE INNODB STATUS\G
# 找到 LOG 部分:
# Log sequence number: 当前 LSN
# Log flushed up to: 已刷到磁盘的 LSN
# Pages flushed up to: 已刷脏页的 LSN
# Last checkpoint at: checkpoint 位置
实战场景
场景:误删数据恢复
# 前提:有全量备份 + binlog 开启
# 1. 恢复全量备份到临时库
mysql -u root -p temp_db < backup.sql
# 2. 找到误删操作的 binlog 位置
mysqlbinlog --start-datetime="2025-01-01 10:00:00" \
mysql-bin.000001 | grep -i "DELETE FROM users"
# 3. 恢复 binlog 到误删前一刻
mysqlbinlog --stop-datetime="2025-01-01 10:29:59" \
mysql-bin.000001 | mysql -u root -p temp_db
# 4. 导出恢复的数据
mysqldump -u root -p temp_db users > users_recovered.sql
# 5. 导入回生产库
mysql -u root -p prod_db < users_recovered.sql
面试模拟
面试官:redo log 和 binlog 有什么区别?
你:1)redo log 是 InnoDB 引擎层的物理日志,记录数据页修改,循环写;binlog 是 Server 层的逻辑日志,记录 SQL 语句,追加写。2)redo log 用于崩溃恢复(crash-safe),binlog 用于主从复制和数据恢复。3)redo log 在事务执行过程中不断写入,binlog 在事务提交时才写入。4)两者通过两阶段提交保证一致性。
面试官:为什么要有两阶段提交?
你:因为 redo log 和 binlog 是两个独立的系统,如果写了一个另一个没写,会导致主从不一致。两阶段提交确保:要么两个都写成功(事务提交),要么两个都没写(事务回滚)。崩溃恢复时,如果 redo log 是 prepare 状态且 binlog 有对应记录就提交,否则回滚。