这是《MySQL 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开,代码块保留了原书可直接运行的版本。 InnoDB 用 Redo Log 保证崩溃恢复,用 Undo Log 支持回滚和 MVCC。两者名字相似,方向相反:Redo 记录“如何重做已提交物理变更”,Undo 记录“如何撤销逻辑变更”。
17.1 两份日志的分工
| 日志 | 方向 | 主要作用 | 内容特征 |
|---|---|---|---|
| Redo Log | 前滚 | 崩溃后恢复已提交修改 | 物理页相关变更 |
| Undo Log | 回滚 | 回滚事务、MVCC 版本链 | 逻辑反操作 |
示例:
UPDATE accounts
SET balance = balance + 100
WHERE id = 1;
Undo 记录逻辑上的旧值:
balance 旧值 -> 500
Redo 记录页面变更:
某个数据页某偏移位置修改为某内容
17.2 WAL:先写日志再写数据页
InnoDB 遵循 Write-Ahead Logging:
1. 修改 Buffer Pool 中的数据页;
2. 生成 Redo;
3. 事务提交前持久化 Redo;
4. 数据页可以稍后刷盘;
5. 崩溃后用 Redo 重放已提交变更。
好处:
- 顺序写替代随机写;
- 提交时不必立刻刷所有数据页;
- 提交持久性由较小日志保证;
- Buffer Pool 可以合并多次修改;
- 崩溃恢复有明确边界。
17.3 Redo Log 结构
查看参数:
SHOW VARIABLES LIKE 'innodb_log_file_size';
SHOW VARIABLES LIKE 'innodb_log_files_in_group';
SHOW VARIABLES LIKE 'innodb_log_buffer_size';
MySQL 8.0.30+ 更推荐:
SHOW VARIABLES LIKE 'innodb_redo_log_capacity';
循环写入:
write pos:当前写入位置
checkpoint:已刷盘数据页对应的水位
write pos 追上 checkpoint 前必须推进 checkpoint
如果 Redo 空间不足:
- 刷脏页加剧;
- 写入抖动;
- checkpoint 阻塞;
Log file is nearly full;- 响应时间尖刺。
查看状态:
SHOW ENGINE INNODB STATUS\G
关注:
LOG
---
Log sequence number
Log flushed up to
Last checkpoint at
17.4 innodb_flush_log_at_trx_commit
参数取值:
| 值 | 行为 | 持久性 | 性能 |
|---|---|---|---|
| 1 | 每次提交刷 Redo 到磁盘 | 最强 | 最低 |
| 2 | 提交写到 OS cache,每秒刷盘 | MySQL 崩溃通常不丢,OS 崩溃可能丢 | 较高 |
| 0 | 每秒写并刷盘 | 崩溃可能丢事务 | 最高 |
查看:
SHOW VARIABLES LIKE 'innodb_flush_log_at_trx_commit';
订单、支付、账户主库建议:
innodb_flush_log_at_trx_commit = 1
sync_binlog = 1
非核心日志库可以结合业务接受度评估 2 或 0,但必须明确故障窗口内可能丢数据。
17.5 Redo 与 Binlog 的区别
| 项目 | Redo Log | Binlog |
|---|---|---|
| 所属层 | InnoDB | Server 层 |
| 内容 | 物理页变更 | 逻辑变更 |
| 用途 | 崩溃恢复 | 复制、归档、时间点恢复 |
| 写法 | 循环覆盖 | 追加文件 |
| 引擎 | InnoDB | 所有引擎 |
| 记录单位 | 页修改 | SQL / 行变更 |
Binlog 常见格式:
| 格式 | 特点 |
|---|---|
| STATEMENT | 记 SQL,体积小,但部分函数和不确定性语句有风险 |
| ROW | 记行镜像,一致性最好,大批量修改日志较大 |
| MIXED | 由 MySQL 选择,历史过渡方案 |
查看:
SHOW VARIABLES LIKE 'binlog_format';
SHOW BINARY LOGS;
SHOW BINLOG EVENTS IN '<log_file_name>' LIMIT 10;
17.6 Undo Log 类型
| 类型 | 场景 |
|---|---|
| insert undo | INSERT 产生,事务提交后可较快清理 |
| update undo | UPDATE / DELETE 产生,需要服务 MVCC |
| purge undo | 清理 delete-mark 记录和历史版本 |
INSERT 的旧版本通常不需要被其他事务读取,提交后清理成本较低。UPDATE / DELETE 的旧版本可能被长事务需要,因此保留更久。
17.7 Undo 与回滚
大事务回滚:
START TRANSACTION;
UPDATE orders
SET status = 'CANCELED'
WHERE created_at < '2025-01-01';
ROLLBACK;
回滚需要按 Undo 执行反向操作:
- 恢复旧值;
- 清理索引修改;
- 释放锁;
- 维护事务状态;
- 记录进度。
大事务回滚可能比执行更久,期间锁和 Undo 仍在。千万不要以为 ROLLBACK 能瞬间撤销所有影响。
查看回滚中的事务:
SELECT
trx_id,
trx_state,
trx_started,
trx_rows_modified,
trx_mysql_thread_id
FROM information_schema.INNODB_TRX;
17.8 Undo 表空间治理
MySQL 8.0 默认使用独立 undo 表空间:
SHOW VARIABLES LIKE 'innodb_undo_directory';
SHOW VARIABLES LIKE 'innodb_undo_tablespaces';
SHOW VARIABLES LIKE 'innodb_undo_log_truncate';
SHOW VARIABLES LIKE 'innodb_max_undo_log_size';
查看:
SELECT
space,
name,
file_type
FROM information_schema.INNODB_TABLESPACES
WHERE name LIKE '%undo%';
膨胀原因:
- 长事务;
- 高频 UPDATE;
- 大事务;
- Purge 延迟;
- 机器 IO 瓶颈;
- 回滚大事务。
处理顺序:
1. 找最老事务;
2. 确认业务是否可终止;
3. 拆分大任务;
4. 优化热点更新;
5. 观察 History list;
6. 最后才考虑空间参数和扩容。
17.9 Buffer Pool 与脏页
查看:
SHOW VARIABLES LIKE 'innodb_buffer_pool_size';
SHOW STATUS LIKE 'Innodb_buffer_pool_wait_free';
SHOW STATUS LIKE 'Innodb_data_pending_writes';
脏页来源:
- 数据页修改;
- 索引页修改;
- Undo 页;
- Change Buffer 合并;
- Adaptive Hash 元数据。
刷脏触发:
- Redo 空间不足;
- Buffer Pool 空闲页不足;
- 后台定期刷新;
- checkpoint;
- 关闭实例;
- 手动刷新。
手动触发:
FLUSH TABLES;
SET GLOBAL innodb_max_dirty_pages_pct = 75;
生产不应频繁手动刷脏,需以监控和容量规划为主。
17.10 Doublewrite Buffer
Redo 记录“对某页应用某变更”,而不是完整页面镜像。如果页本身发生部分写坏,仅靠 Redo 无法安全重放。
Doublewrite 流程:
1. 脏页先写入 doublewrite 区域;
2. 刷盘;
3. 再写入表空间对应位置;
崩溃后发现页损坏时,可从 doublewrite 恢复完整页,再应用 Redo。
查看:
SHOW VARIABLES LIKE 'innodb_doublewrite';
SHOW STATUS LIKE 'Innodb_dblwr_pages_written';
关闭 Doublewrite 可能提升一点性能,但以牺牲部分写保护为代价。生产默认保持开启。
17.11 崩溃恢复流程
简化流程:
1. 读取最近 checkpoint;
2. 扫描 Redo;
3. 重放必要页面变更;
4. 恢复 Undo;
5. 根据事务状态回滚未提交事务;
6. 与 Binlog 协调 prepared 事务;
7. 实例可对外服务。
大事务、大量脏页、Redo 很长、IO 故障都会拉长恢复时间。高可用设计必须考虑重建时间,而不是只关心平时 QPS。
17.12 生产实践
- 核心主库保持双 1;
- 控制事务大小;
- 避免大范围 UPDATE / DELETE;
- 监控 Redo 等待和 checkpoint;
- 监控 Undo 表空间和 History list;
- 大任务按主键或时间分批;
- 压测新参数;
- 备份和恢复演练必须覆盖崩溃场景;
- 磁盘必须使用掉电保护或可靠云盘;
- 不用非持久化参数换取虚假性能。
本章小结
Redo Log 通过 WAL 和崩溃恢复保证已提交修改的持久性,Undo Log 支持事务回滚和 MVCC。innodb_flush_log_at_trx_commit=1 提供最强持久性;Doublewrite 解决部分写坏页问题;长事务和大事务会拖累 Undo、Purge、锁和恢复时间。
思考题
- Redo Log 和 Undo Log 的方向有什么区别?
- 为什么提交时刷 Redo 而不是刷所有数据页?
innodb_flush_log_at_trx_commit三个取值分别意味着什么?- Doublewrite Buffer 解决什么问题?
- 大事务回滚为什么可能很慢?如何避免?