第 4 / 23 篇

第3章:提交、日志与确认的三条边界

客户端收到COMMIT成功,副本是否已经拥有这笔交易?备份包含某个订单,对应的binlog位置是否就在文件结尾?恢复问题经常来自把不同边界当成一个时刻。本章沿着一次下单,从源库内部提交走到客户端确认,再走到副本应用,解释后续实验为什么需要多种证据。

从业务事务开始

一笔订单包含三条修改:订单行、支出分录和账户余额。实验明确使用START TRANSACTION与COMMIT,使三项变化具有同一事务边界。任何一项失败都不能被当作完整业务成功。程序在每次实验后查询不变量,避免某个SQL错误被后续步骤掩盖。

InnoDB的redo主要服务于存储引擎崩溃恢复,binlog保存服务器层可用于复制与恢复的变化事件。它们不是可以随意互换的两份相同日志。操作人员通常用备份加binlog进行目标时间恢复,不会把某个redo文件直接送给mysql客户端。理解用途差异,也能避免看到“日志还在”就宣布可恢复。

源库内部需要协调事务与二进制日志的提交,具体持久性受参数、文件系统与设备保障影响。本书记录sync_binlog和innodb_flush_log_at_trx_commit,但不通过简单读变量宣称机器断电不会丢事务。容器正常停止与物理断电不同,源进程停止也不等于磁盘永久丢失。我们后面证明的是异步副本缺少已确认事务,而非磁盘持久性极限。

客户端成功也是一个观测

实验中的成功确认是mysql客户端返回零退出码。客户端返回前事务已经被服务器接受完成,但这个事实没有要求异步副本同步提交。若网络在提交成功后断开,客户端还可能看不到确认,形成结果未知。本书不把这种情况纳入RPO计数;示范丢失的那笔交易必须先明确得到源库成功回执。

生产系统需要保留稳定业务请求键。恢复后,应用可能重复提交一笔自己没有收到响应的请求;缺少幂等约束就可能重复扣款。数据库恢复解决的是某个数据状态,客户端恢复解决的是请求是否可以安全重试。两个流程必须通过订单号或流水号衔接,不能只靠数据库连接重新建立就认为业务已恢复。

接收与应用分开

异步复制至少涉及源端写出、网络传输、副本接收保存和副本执行。暂停SQL应用线程时,接收线程仍可能持续取得日志。此时“日志到了副本”和“查询能看到事务”有不同答案。把线程状态或接收位置当成业务可读状态,会让切换决策过早。

实验先停止副本SQL线程,提交一笔带标签的订单,确认副本业务状态保持不变,并用GTID集合判断目标事务尚未执行。随后恢复SQL线程,等待指定GTID集合执行完成,再逐行比较业务状态。等待函数成功是同步条件,业务比较是结果条件,二者共同约束结论。

最后的丢失实验则停止整个复制通道,保证新事务根本不会被接收。这样我们能确定地构造一笔源端已确认、副本没有的交易。这个故障注入不是随机网络延迟模拟,也不声称复现所有现实故障;它把异步协议允许的窗口固定下来,便于读者精确观察后果。

文件位置指向事件边界

二进制日志位置是文件内字节偏移,必须与文件名一起使用。不同文件的同一个数字没有共同业务含义。恢复开始位置来自逻辑备份记录的坐标,结束位置取自两个保留事务都提交之后、误删事务开始之前的服务器状态。我们不截断一个事务的中间部分来凑出想要的行数。

ROW格式保存行变化,mysqlbinlog的解释输出帮助人理解事件;真正回放仍使用可执行输出。把带解释注释的展示文本手工改成UPDATE语句,会产生一套未经验证的新恢复程序。我们保持日志原义,选取连续的事务前缀,再用业务状态确认边界正确。

GTID提供事务身份,文件位置提供某个日志流的具体切点。GTID集合包含事务,不意味着这些事务的业务关系仍然符合你的期望;误删也是一个有合法GTID的成功事务。要排除它,仍需知道要恢复到哪个业务目标。技术标识使定位更可靠,却不会替人选择该保留哪笔交易。

一个需要拒绝的推论

假设源成功返回,副本接收线程显示连接正常,就立即把旧主停止并提升副本。这个推论缺少事务实际接收与执行的证据。正常连接只表示通道某次握手成功,不是每个业务提交的同步回执。正确决策需要固定目标水位,检查副本拥有什么,并在无法补齐时明确丢失集合。即使之后发现源盘完好,也不能倒过来宣称当时的切换已经无损;补救需要额外日志、业务核对和独立审计。

本章练习

画出源端提交、客户端收到成功、副本接收、副本执行四个事件的先后关系。哪些事件在异步复制下不能由客户端成功推出?再描述一种客户端未收到成功但源库已经提交的情况。解释为什么恢复后仅查询某个GTID存在,仍不能证明客户订单已得到正确处理。