必须先查清问题根源再操作,严禁盲目UPDATE;所有修复须在预发库验证、加事务和行数检查、用业务主键精准定位、修复后反向验证并留痕。

先确认是否真要直接 UPDATE
多数所谓“数据修复”其实不该动生产库——先查清问题根源,比如是应用逻辑写错、ETL 脚本漏处理,还是上游系统传入脏数据。盲目跑 UPDATE 可能放大错误,尤其当条件没写全或没加 WHERE 时,UPDATE users SET status = 'active' 这种语句在没 WHERE 的情况下会全表生效。
- 优先用只读语句验证:先
SELECT出待修复的数据,核对数量、范围、样例值 - 如果涉及多表关联修复,先用
LEFT JOIN+WHERE ... IS NULL确认缺失/错位关系,别一上来就UPDATE ... FROM - 禁止在未备份的生产环境执行任何写操作;哪怕只是测试,也要在同结构、同数据量的预发库跑通
写 UPDATE 前必须加事务和行数检查
SQL 修复脚本不是“写完就能跑”,关键在于可控、可退、可验。裸跑 UPDATE 没有回滚预案等于埋雷。
- 所有修复语句必须包裹在
BEGIN TRANSACTION/COMMIT或ROLLBACK中(SQL Server);MySQL 用START TRANSACTION - 执行前加校验:用
SELECT COUNT(*)统计将被影响的行数,和预期值比对;差异大就中止,别硬上 - 加上
TOP N(SQL Server)或LIMIT(PostgreSQL/MySQL)做限流,比如先试改 10 行,人工确认结果再放开 - 避免用子查询当
SET值,除非明确知道它只返回一行;否则可能报错或静默取第一行,导致数据不一致
WHERE 条件必须带业务主键或唯一约束字段
用时间范围或状态字段做 WHERE 很常见,但极容易误伤——比如修复“2024-05-01 的订单状态”,结果把当天所有 status = 'pending' 的都改成 'processed',却漏了其中部分已人工干预的记录。
- 优先用业务主键:如
WHERE order_id IN (1001, 1002, 1005),而不是WHERE created_at = '2024-05-01' AND status = 'pending' - 如果必须用范围条件,叠加唯一性字段组合,例如
WHERE date = '2024-05-01' AND shop_id = 123 AND order_id IS NOT NULL - 禁止用模糊匹配(如
LIKE '%abc%')做修复依据,除非你 100% 确认该模式只命中目标数据
修复后必须做反向验证和日志留痕
改完不验证 = 不知道改没改对;没日志 = 出事查不到谁、什么时候、改了什么。
- 验证不能只看“没报错”,要查修复后的数据是否符合业务语义:比如金额字段是否为正、状态流转是否合规、外键是否仍有效
- 用
EXCEPT或NOT EXISTS做前后比对,例如SELECT order_id, status FROM orders WHERE ... EXCEPT SELECT order_id, status FROM backup_orders WHERE ... - 把完整脚本(含注释、影响行数、执行人、时间戳)存进运维日志表或 Git;临时写的 ad-hoc 脚本最容易丢,也最容易重复执行
- 如果修复涉及金额、用户身份等敏感字段,必须走双人复核流程,且脚本里显式写明
-- REVIEWED_BY: alice, bob
真正危险的不是不会写 SQL,而是写得太顺——几行 UPDATE 就想“快速搞定”,结果把修复变成事故。最常被跳过的其实是那句 SELECT COUNT(*) 和那个没加的事务头。










