第16章:用业务核对阻止漂亮但错误的恢复结果
恢复系统最容易产生一种危险的自信:进程启动了、表都在、线程没报错,于是任务结束。真正的问题往往藏在对象对应、金额、边界和分叉里。本章把随书state函数拆开,说明每项检查覆盖什么,并建立从技术过程到业务验收的分层证据。
第一层是执行过程
工具退出码检查很基础,却常被shell管道和重定向吞掉。mysqldump失败可能留下部分文件,mysqlbinlog失败可能只产生头部文本,mysql导入失败可能已经提交前面一些语句。程序对每个关键子进程检查返回值,异常立即停止,避免后续成功查询掩盖前序失败。
负例则需要相反判断:必须非零,并且错误是预期原因。只读拒绝明确要求ERROR 1290与read-only语义;缺归档要求实际必需文件缺失时工具失败。随后再查业务状态没有改变。这样可以区分“保护机制工作”和“命令根本没有执行到目标”。
第二层是数据库结构与对象
表存在、列类型和主键约束正确,是业务数据能够解释的前提。教学库由同一个dump恢复,完整构建验证会比较网页、EPUB与源码,但数据库结构仍主要由真实导入证明。扩展到生产时,应独立核对字符集、索引、分区、外键、触发器和存储程序,不只比较CREATE TABLE文本。
对象行为也可能受sql_mode、时区和权限影响。相同SQL在不同环境可能有不同结果,恢复报告应记录关键变量和实际服务账户测试。本书记录了与日志和持久性有关的变量,没有声称覆盖所有配置。验证范围写清楚,比罗列很多未执行检查更可信。
第三层是关系与总账
state读取余额和分录总和,要求余额等于十万加delta。随后核对分录数量与订单数量,查找没有订单或金额不匹配的分录。entries.order_id有唯一约束,结合数量相等和无孤立错误,使每个订单必须恰有一条对应分录。完整订单行包含主键、金额与业务标签。
这里的逻辑依赖实验表结构:唯一约束是检查推理的一部分。若去掉它,同样的数量和孤立查询可能不能证明一一对应,因为一个订单可以重复对应而另一个缺失。任何验证器都依赖数据模型,应把这些前提写出来,不能把函数名称叫validate就视为普遍证明。
第四层是目标集合
一致快照的目标不是源最终数据,因此只要求恢复状态位于导出观测窗口内并满足关系。安静基线要求与源最终状态完全相等。PITR要求与误删前保存的目标相等,并排除误删后订单。故障切换要求幸存前缀加新主专属订单,同时明确缺失源端独有订单。
四种目标各有参照,不能复用一条“源副本相同”断言。若PITR最后与当前误删源相同,反而是失败;若异步故障切换与故障前源完全相同,可能说明没有真正制造丢失窗口。测试应从预期业务命题推导,而不是为了方便通过随时更换比较对象。
第五层是外部确认
数据库内部检查不能知道客户收到了什么。实验把mysql客户端成功返回作为确认,并在报告保留源故障前状态。生产应与独立请求日志、支付系统和下游回执核对。没有外部确认集合时,只能说恢复库内部一致,不能承诺所有已确认交易都被保留。
这层核对还帮助处理结果未知。某次客户端超时可能已提交,也可能未提交。恢复后通过稳定订单号查询,再按幂等协议决定是否重试。若应用每次重试生成新订单ID,数据库恢复本身再正确,也无法阻止客户被重复扣款。
摘要是索引,不是解释
报告同时保存摘要和有序行,便于快速判断相等及定位差异。大数据场景可以保存分块摘要与差异清单,但应保持规范化编码版本。摘要计算方式改变后,旧报告可能无法直接比较,需要标识算法和字段范围。不要把一个十六进制字符串当作无需解释的质量标签。
本书的业务数据有限,报告可读性比极致压缩更重要。一次PITR恢复一百二十三个订单时,读者可以独立核算总支出二百六十、余额九万九千七百四十。显式数值与机器摘要相互支持,也让编辑审稿能发现文字与代码是否一致。
为失败保留足够信息
实验异常会保存FAIL报告和已完成场景,CI始终上传证据。这样可以看到失败发生在启动、备份、归档、复制还是提升。生产还需保护敏感数据,对诊断信息做权限和保留控制。教学标签不是客户数据,我们才允许直接存储完整有序行;不能把这种做法无条件搬到真实个人信息表上。