先明确本讲的讨论范围
这里讨论启用 binlog 的 MySQL 与 InnoDB,以及一条更新语句所在事务的提交过程。连接、分析和优化仍然存在;新增的关键问题是:内存中的修改如何变得可恢复,两套日志如何对同一事务形成一致的提交结论。文中的路径是便于理解的简化模型,不等同于所有版本的源码调用顺序。
WAL:推迟刷数据页,不是完全不写磁盘
例如 UPDATE counters SET value = value + 1 WHERE id = 7。数据页可能先被读入内存,再产生修改;没有必要让事务每次都等待对应数据页写回磁盘。WAL 要求相关日志先于脏数据页持久化。日志本身也需要 I/O,其收益来自顺序写、写入合并等机制,而不是“只写内存就能保证安全”。提交与刷脏页是不同事件,不能画上等号。
两份日志,各自回答不同的问题
- redo log:由 InnoDB 管理,描述与数据页修改有关的重做信息,用于崩溃恢复。它不是长期保留所有业务操作的历史账本。
- binlog:由 Server 层管理,记录语句事件或行变更事件,可用于复制和配合备份恢复。不能简单把它理解为一份 SQL 文本列表。
- redo 空间会被循环复用,但必须先满足恢复安全条件;binlog 以追加和切换文件的方式记录,也会受保留与清理策略影响,不会永久自动保存。
一次更新,沿着提交边界看
先假设记录存在,并且事务最终选择提交。如果是显式事务,多条语句可以属于同一次提交;不能把下面的日志协调理解为每条 UPDATE 都独立提交。
- 执行器通过引擎取得目标行,计算新值,再调用引擎完成修改。
- InnoDB 修改内存中的数据页并生成 redo,在提交协调中进入 prepare 状态。
- Server 层将该事务的 binlog 纳入提交过程,满足相应持久化要求。
- 引擎完成 commit,事务结束。脏页可以随后按刷盘策略写回数据文件。
为什么需要 prepare 和 commit
如果让两份日志各自独立提交,中间发生崩溃就可能产生分歧:引擎已经认可事务,binlog 却缺失;或者 binlog 已包含事务,引擎却没有可恢复的对应修改。前者会让基于日志重放的库少一次更新,后者会让它多一次更新。两阶段提交让 Server 层和引擎围绕同一个事务协调结果,而不是仅仅规定两个文件谁先写。这里是数据库内部协调,不是说一次普通 UPDATE 要由应用发起跨服务分布式事务。
崩溃发生在中间,如何理解结果
以下是课程模型下的两种关键情况。判断依据是崩溃后可恢复且完整的日志信息,不能把“调用过写入函数”直接当成“已经可靠落盘”。
- 只有 prepare,没有匹配的完整 binlog:不能把它当成已提交事务,恢复时应回滚。
- 已经 prepare,且存在匹配的完整 binlog,但 commit 尚未完成:恢复过程可以完成提交,使引擎状态与日志重放结果一致。
- 因此,看不到最终 commit 标记不等于一定回滚。是否已向客户端返回成功,与恢复时如何认定事务,也不是同一个问题。
恢复到某个时间点,需要完整的恢复链
恢复误操作前的数据,通常需要一个早于目标时点的有效全量备份,以及从备份对应位置到目标位置的连续 binlog。先在独立环境恢复备份,再重放到误操作之前,核对后决定如何取回业务数据。不能把误操作后的所有事件盲目重放到修复库:后续业务可能已经基于错误状态继续写入。这里整理的是恢复思路,没有在真实数据库执行恢复操作。
参数与版本,别记成绝对承诺
- 课程介绍 innodb_flush_log_at_trx_commit=1 与 sync_binlog=1,用于加强 redo 和 binlog 的提交持久性。实际效果仍依赖系统与存储正确兑现持久化语义,不等于可以防住磁盘损坏或所有灾难。
- redo 的文件数量和容量示例不是所有版本的默认值;配置方式与具体实现需要按部署版本核对。
- binlog 可采用语句、行或混合格式。行事件内容还受配置影响,不能断言所有情况下都完整记录每列的前后镜像。
- 日志协调也不意味着 redo 单独实现了全部 ACID 特性。回滚、隔离与并发控制还有其他机制参与。
复习:每天全备比每周全备好在哪里
在日志完整且目标时点相同的前提下,更近的全量备份通常意味着需要重放的日志更少,主要改善恢复耗时,也就是 RTO。它同时增加备份存储与执行成本。允许丢失多少数据(RPO)还取决于日志持久化、异地保存等策略,不能仅凭全备频率下结论。
本讲留下的检查顺序
- 先分清内存修改、日志持久化、事务提交、数据页刷盘四个动作。
- 再比较 redo 的崩溃恢复职责与 binlog 的归档、复制职责。
- 最后用崩溃时点推演两阶段提交,而不是只背 prepare → binlog → commit。
