知识全景图/ 软件工程与系统/ 数据库生产治理/ 如何设计数据库生产数据修复、人工变更与安全操作

如何设计数据库生产数据修复、人工变更与安全操作

不要把修复当反向 UPDATE;先证明“修的是谁,改回什么,怎么不再误伤”

这一页只解决一个问题: 生产数据库已经被坏脚本、坏迁移或坏批任务写错了,不能整库回滚,只能定向修复时,怎样设计对象范围、旧值证据、dry-run、分批执行和修复后复核。
一、事故:分类合并脚本改坏真实账目

本来只想把测试租户里的“餐饮”合并到“交通”,脚本条件写宽,影响了 163 个真实租户。最危险的反应是马上写一条反向 UPDATE,因为有些“交通”本来就是正确分类。

bad update: UPDATE ledger_entries SET category_id = 'transport' WHERE category_id = 'food' AND source = 'imported'; real issue: no tenant whitelist no candidate table no old-value evidence
错误反应为什么危险先做什么
马上反向 UPDATE会把本来正确的交通改坏先固化被坏脚本影响的对象集合
整库恢复备份会覆盖事故后的正常新增账目恢复到隔离库,只抽旧值证据
客服手工改漏修、错修、无审计,派生报表仍错走候选表、dry-run、分批修复和复核
二、修复前只问 6 个问题
问题例子
坏操作是什么SQL、执行人、时间、影响行数、事务日志位点
修复对象能否固化把 entry_id 写入 repair_candidates,不靠临时 WHERE
旧值从哪里来恢复库、审计表、binlog / WAL、业务事件、导入原文件
执行时如何避免覆盖新改动WHERE current_value = bad_value,不覆盖用户已修正值
哪些派生数据也被污染缓存、搜索、月报宽表、预算汇总、数据仓库
是否应该走业务补偿而不是 UPDATE流水、支付、余额、权益通常不能直接覆盖
三、最小修复模型
repair_candidates - repair_id - entry_id - tenant_id - bad_value - expected_value - evidence_source - evidence_ref - status: pending | repaired | skipped | failed - reason repair rule: UPDATE ledger_entries e JOIN repair_candidates c ON e.id = c.entry_id SET e.category_id = c.expected_value WHERE c.repair_id = ? AND c.status = 'pending' AND e.category_id = c.bad_value;
吸收点: 修复对象和修复动作要分开。先固化候选对象,再执行 UPDATE;否则 WHERE 条件一变,第二刀更深。
四、dry-run 不是预计影响行数
dry-run 必须输出说明什么
影响租户、账目、月份、分类分布范围是否符合事故窗口
修复前后样本旧值、新值、证据来源是否能解释
反例样本相似但不该修的数据没有混进来
约束和不变量检查外键、状态、余额、月报口径不会被破坏
资源和批次计划锁范围、批次数、预计耗时、暂停条件清楚
关键边界: 没有可信旧值,就不要批量改回去。宁可标记待人工核验,也不要用想象修复生产数据。
五、用 6 个样本证明修复可信
样本通过标准
候选集混入不该修的数据反例样本或当前值校验能拦住
脚本跑到一半中断按 status 续跑,不重复扩大影响
用户已经手工改回当前值不等于 bad_value 时跳过,不覆盖用户新值
主库修好但搜索仍旧派生副本复核能发现并触发重建
月报分类金额仍错按受影响月份重算,不全量乱跑
账务流水金额错判断走冲正 / 补偿,不直接覆盖原流水
六、下一步只按修复来源下钻
如果旧值来自备份
如果坏数据来自迁移
如果派生结果也要修
如果要防止下次再发生
总结: 生产数据修复不是“写反向 UPDATE”。先冻结证据,固化候选对象,找到可信旧值,dry-run 给出样本和反例,再小批次、幂等、可续跑地执行,最后复核主库和派生数据。修复的第一目标不是快,而是不误伤。