MySQL 三种日志:提交、崩溃恢复与复制边界
一次 UPDATE 已返回成功,数据页却可能还没写回磁盘;一次 COMMIT 连接中断,也不能直接断言事务失败。理解这些现象,需要把“页面怎样恢复”“事务怎样撤销”“变更怎样传播”分开。下文以 MySQL 8.4、普通持久化 InnoDB 表、启用 binlog 为前提,不涉及非事务表与应用发起的分布式 XA。
三种日志各自解决什么问题
| 日志 | 所属层与内容 | 主要职责 | 不能替代什么 |
|---|---|---|---|
| redo | InnoDB,页面修改的重做记录 | 崩溃后补齐尚未落盘的修改 | 不能按业务要求撤销误删 |
| undo | InnoDB,重建修改前状态所需的信息 | 事务回滚、MVCC 旧版本读取 | 不是完整历史备份 |
| binlog | MySQL Server,数据库变更事件 | 复制、配合备份做时间点恢复 | 不能直接修复 InnoDB 数据页 |
redo 通常按页面层面的变化理解,并非原 SQL 副本。它循环复用空间,检查点推进后,已不再需要的旧记录可被覆盖。undo 也不是简单保存一条“反向 SQL”:更新记录需要保留到没有旧快照依赖时,才可由 purge 清理。普通持久化表的 undo 修改自身也受到 redo 保护,临时表 undo 有不同规则。
binlog 的 ROW 格式描述行变化,STATEMENT 格式记录语句,不能统称为“所有 SQL 的文本日志”。普通 SELECT 不在其记录范围内。三者不能互换,因为恢复所需的粒度、生命周期和使用者均不同。
从更新到提交:先保证可恢复,再延后刷脏页
下面只展示事务语义,需在独立测试库执行。本文未连接数据库,注释为预期推导,不是运行输出。
CREATE TABLE log_demo (
id INT PRIMARY KEY,
amount INT NOT NULL
) ENGINE=InnoDB;
INSERT INTO log_demo VALUES (1, 100);
START TRANSACTION;
UPDATE log_demo SET amount = amount - 10 WHERE id = 1;
ROLLBACK;
SELECT amount FROM log_demo WHERE id = 1; -- 预期 100更新过程中,InnoDB 产生 undo、修改缓冲池页面并生成 redo。WAL 的关键约束是:脏页落盘前,相应 redo 必须先持久化;它不要求每次提交同步写回全部数据页。未提交事务的页面也可能被刷出,因此恢复不能只重做“提交过的页面”。
崩溃恢复先应用必要 redo,使页面回到可恢复状态,再处理未完成事务的回滚。redo 负责重做,undo 负责撤销,两者共同处理崩溃前可能已经写入磁盘的未提交修改。删除 redo 文件不是恢复方法;磁盘损坏也不能靠这些日志保证修复。
两阶段协调:让本机数据与 binlog 作同一决定
若先独立提交 InnoDB,再写 binlog,中间崩溃可能出现本机有数据而复制端没有;反过来则可能出现 binlog 有事务而本机回滚。内部两阶段提交通过 prepare 保存“可以提交、尚待裁决”的状态。
在日志可靠持久化的前提下,恢复发现普通内部事务处于 prepare,会检查有效 binlog 中是否有相应的完整事务决定:存在则完成提交,不存在则回滚。已持久化提交状态的事务按已提交处理。这里不能把“出现部分 binlog 字节”当成“完整提交记录”。规则见官方 Binary Log 的崩溃协调说明。
这也解释了断线的歧义:服务器可能已经完成提交,只是成功响应没有送达。应用应凭业务唯一键或事务状态查询确认,直接重试扣款可能重复执行。内部协调不需要业务发送 XA PREPARE,也没有让普通 COMMIT 自动等待所有副本。
刷盘参数决定承诺能承受哪类故障
innodb_flush_log_at_trx_commit 为 1 时,每次事务提交要求写入并刷持久化 redo;为 2 时提交写到操作系统缓存,通常每秒刷盘;为 0 时写入和刷盘通常周期执行。周期不是严格的一秒上限,进程崩溃与操作系统或断电故障也有不同影响。
sync_binlog=1 要求每个 binlog 提交组同步日志;0 交由操作系统决定同步,较大值会留下更多尚未同步的提交组。“双 1”常用于追求事务持久性,组提交可摊薄同步成本,但仍依赖文件系统和存储正确兑现同步请求。两阶段协调不能凭空找回丢失的日志,也不等于抵御整块磁盘损毁。
复制与备份:传播记录不等于保留历史
复制消费源库 binlog,经 relay log 后在副本应用。异步复制下,源库提交成功不代表副本已经收到或应用;副本延迟会影响故障切换的数据边界。两阶段提交保障的是本机 InnoDB 与 binlog 的一致决定,不能据此推导跨节点零丢失。
时间点恢复需要一个可用的基线备份、与其一致的起始位置,以及此后连续保留的 binlog。先恢复基线,再用 mysqlbinlog 提取并应用目标边界之前的完整事务。具体步骤见官方 时间点恢复。时间可用于定位候选区域,最终应核对事务和事件位置,避免截断事务或重复应用基线已包含的部分。
误删也会传播到副本;redo 又会循环覆盖,undo 会被清理,所以三种日志都不能单独替代备份。恢复误操作通常应在隔离实例重建到误操作之前,再核对所需数据;直接把全部 binlog 回放到线上,会把误删再次执行。
继续阅读与示例边界
可结合本站 MySQL MVCC 理解 undo 与旧版本,再读 事务隔离 和 索引原理。
本例只有 SQL,无 Java 示例。当前 PATH 发现的是 MySQL 5.7 程序,未建立与本文版本匹配的运行环境;SQL、复制、备份及崩溃注入均未执行。文中的时序和预期结果来自官方机制说明,不代表故障演练证据。
