第 15 章:报错以后,先查清数据库留下什么
错误处理如果只有“捕获异常后重试”,就把三种不同问题压成了一种动作:死锁受害事务已经整体撤销,默认锁等待超时可能保留前序修改,提交响应丢失又可能意味着业务已经成功。恢复单位必须匹配数据库事实。
15.1 对照 1213 与 1205
先单独运行两种竞争:
python3 -m unittest \
lab.scenarios.InnoDBCases.test_09_deadlock_rolls_back_one_entire_transaction \
lab.scenarios.InnoDBCases.test_10_lock_timeout_does_not_rollback_prior_statement_by_default -v死锁场景让两个连接分别先更新不同账户,通过屏障后再争用对方的行。结果一份提交、一份 deadlock_1213,最终余额为 [101,101]。受害者第一条更新没有残留;谁成为受害者不被测试固定,恢复时应重新读取并执行完整业务事务。
当前测试在 1213 后故意不先 ROLLBACK,直接发送一次 COMMIT,再由独立连接检查余额。这样可以排除“只是应用主动回滚才消除了残留”的解释。实验报告将此动作记为 victim_final_action=commit_without_explicit_rollback;较早的实验报告没有这个字段,不能用它单独证明这项增强断言。
1205 场景的前提是 innodb_rollback_on_timeout=0。等待者先成功修改账户 2,再在账户 1 超时。实验故意继续 COMMIT,最后持锁者回滚,得到 [100,105]。它展示了不完整业务仍可能被提交,而不是推荐在报错后这样处理。
将会话锁等待上限从一秒改为两秒,可以研究等待期限,而不能改变默认回滚范围。若另开实例启用整事务超时回滚,必须同时修改环境合同与预期;不要删除配置断言后把两种行为混进同一报告。
15.2 真正丢掉 COMMIT 确认
提交未知场景使用专用环回代理。它转发客户端 COMMIT,在观察到服务器 OK 后切断连接,故意不让确认到达调用方。真实节选为:
{"client_error_code":2013,
"server_commit_ok_observed":true,
"independently_found_order":1}2013 是这次驱动与断连路径的实际结果,不应把它当成所有网络故障的统一代码。关键证据是代理确实等到了服务器提交确认,而独立连接又按原操作键找到一个订单。客户端报错与数据库提交成功在这里同时成立。
python3 -m unittest \
lab.scenarios.InnoDBCases.test_14_commit_reply_lost_can_still_mean_committed -v代理按明文 MySQL 包识别命令,无法理解 TLS 加密后的 SQL。仅该故障连接通过 ssl_disabled=True 明确使用环回明文;普通连接保留 PyMySQL 的原有 TLS 行为。此前握手失败的修复限定在这个注入入口,不是让所有连接统一关闭传输保护,也没有额外证明生产证书校验策略。
15.3 查询原业务身份,再决定恢复
独立控制连接以自动提交方式读取同一实例,不沿用可能过期的 RR 视图,也没有从复制副本查询。找到订单后还应在真实系统核对参数、状态与结果。若查询暂时为空,仍需考虑提交时序与读取位置,不能立即换一个新操作键重做。
本例在服务器已经返回 OK 的确定位置断线,所以独立查询应找到订单。若你把故障点移到发送 COMMIT 之前,就变成另一项实验;必须重新定义已知阶段、断言与清理,不能沿用这个输出作为证明。
对于业务原子事务,1213 或默认 1205 后的恢复通常包括显式结束当前事务、重新读取和受限重试;对 unknown 则先保存身份并核查。重试次数、截止时间和事务外副作用还要有自己的约束。一个统一装饰器无法从异常名称自动推导这些责任。
核查必须发生在测试清理之前。本场景先由独立连接找到订单、记录结果,再由 tearDown 删除专用随机库。稍后查不到该库,是清理生命周期结束,不是原 COMMIT 被回滚。保留下来的证据应写明观察顺序,否则同一段“找不到记录”的描述可能指向完全不同的事实。
15.4 Outbox 只补上本地缺口
最后把订单与 outbox 放进同一事务。提交一组、回滚另一组后,两张表各剩一行;队列式认领则观察到工作者取得 1、2,第一位回滚后另一位重新取得 1。它们验证本地原子性、竞争与恢复机会,没有连接 Broker 证明消息发出,更没有证明全链路恰好一次。
发布成功但 sent 标记未更新,仍可能再次发送;消费者因此还需要稳定事件身份与自己的事务。数据库实验证明哪一段,就把结论留在哪一段。复制、备份、断电恢复与跨库故障没有在这十五项中执行,不能从单节点成功推导它们也成立。
练习。 给 1213、1205 和提交未知各写一张恢复卡:已知事实是什么,哪些前序工作仍可能存在,下一步查询或回滚针对谁,以及什么条件下允许再次执行原业务。至少指出一项不能靠数据库 ROLLBACK 撤销的外部动作。
错误处理与锁定读取的官方依据见资料附录,实际结果以本次 CI 的 JSON 和提交为准。