AI 辅助生成数据修复脚本前,先做回滚清单:一次 UPDATE 漏写范围条件的防线设计

AI 辅助生成数据修复脚本前,先做回滚清单:一次 UPDATE 漏写范围条件的防线设计 线上系统里经常会出现一种“不算故障、但必须处理”的数据问题。例如任务调度模块在一次发布中断后少量执行记录停留在RUNNING状态。它们实际上已经不再运行却也没有进入可重试或失败状态后续流程因此一直被卡住。这时最容易想到的修复方式是跑一条更新语句UPDATEtask_executionSETstatusRETRYABLE,updated_atNOW()WHEREstatusRUNNING;从语法上看它没有问题。从业务意图上看也像是在把“卡住的任务”恢复到可处理状态。但真正危险的地方在于数据库里的RUNNING不一定都代表“卡住”。其中可能同时存在刚刚开始执行、仍然正常的任务正在被另一台节点处理的任务因网络抖动暂时没有上报心跳的任务已经被人工介入、等待确认的任务历史残留数据某个特殊分区中不允许自动重试的任务。如果没有先定义候选范围这条语句会把所有运行中的任务一起改掉。表面上修复完成了。实际上正在处理中的任务可能被重复调度本应等待人工确认的记录被自动推进原本可追踪的异常也被一次粗粒度更新覆盖掉了。这类事故的根源通常不是 SQL 写错而是“修复目标”从一开始就没有被建模。一、最常见的错误先让 AI 给 SQL再补边界很多开发者向 AI 描述问题时往往是这样一句话有些任务长期处于 RUNNING 状态帮我写一条 SQL 把它们改成可重试。这种输入会自然得到类似结果UPDATEtask_executionSETstatusRETRYABLE,retry_countretry_count1WHEREstatusRUNNING;AI 没有“理解错”。它只是按你给出的范围完成了一个局部任务。问题是输入里没有告诉它“长期”具体是多少分钟、多少小时是否要排除仍有心跳的记录是否要限制到某次发布前创建的数据是否要按租户、分区或任务类型过滤是否允许并发节点继续更新这些记录已经被人工标记的异常是否需要跳过一次最多处理多少条失败后如何恢复到执行前状态。批量修复最怕的不是语法错误而是“语法正确、范围错误”。因为语法错误通常会立即报错而范围错误往往会完整执行。二、不要直接修改目标表先固化候选集更稳妥的做法是先把“准备修复的记录”单独落到候选集里。例如建立一个批次表CREATETABLEtask_repair_candidate(batch_idVARCHAR(64)NOTNULL,task_idBIGINTNOTNULL,expected_statusVARCHAR(32)NOTNULL,expected_versionBIGINTNOTNULL,selected_atDATETIMENOTNULL,PRIMARYKEY(batch_id,task_id));第一步不是更新而是筛选。INSERTINTOtask_repair_candidate(batch_id,task_id,expected_status,expected_version,selected_at)SELECT:batchId,id,status,version,NOW()FROMtask_executionWHEREstatusRUNNINGANDlast_heartbeat_at:cutoffTimeANDupdated_at:cutoffTimeANDmanual_hold0ANDtask_typeIN(SYNC,EXPORT)LIMIT200;这一步的价值很大。它把“直接改变数据”拆成了两件事先确定候选范围 ↓ 再审查候选范围 ↓ 最后执行受约束的更新在真正执行前团队可以先检查SELECTtask_type,COUNT(*)AScandidate_count,MIN(last_heartbeat_at)ASearliest_heartbeat,MAX(last_heartbeat_at)ASlatest_heartbeatFROMtask_repair_candidate cJOINtask_execution tONc.task_idt.idWHEREc.batch_id:batchIdGROUPBYtask_type;这比直接看到“更新了 183 条”更有价值。因为它能让人先判断这 183 条是不是预期中的那 183 条。三、候选集正确不代表执行时仍然安全即使候选集筛对了执行阶段仍然可能发生变化。例如你筛选候选集时某条任务确实已经 30 分钟没有心跳但在执行更新前它可能恢复了或者被其他节点重新接管。因此最终更新必须带上“二次校验条件”。UPDATEtask_execution tJOINtask_repair_candidate cONc.task_idt.idSETt.statusRETRYABLE,t.retry_countt.retry_count1,t.repair_batch_id:batchId,t.updated_atNOW(),t.versiont.version1WHEREc.batch_id:batchIdANDt.statusc.expected_statusANDt.versionc.expected_versionANDt.last_heartbeat_at:cutoffTimeANDt.manual_hold0;这里的version很重要。它代表候选集建立之后如果这条记录已经被其他流程修改过那么本次修复不应该再强行覆盖它。这本质上是一次乐观锁校验。如果候选集有 200 条最后只更新了 187 条不要急着认为“有 13 条执行失败”。更合理的理解是这 13 条在处理期间发生了变化它们可能已经恢复、被人工处理或被其他节点接管需要单独查明不应自动再跑一次同样的 SQL。四、错误但常见的处理方式一次性开事务更新全部数据另一种常见做法是STARTTRANSACTION;UPDATEtask_executionSETstatusRETRYABLEWHEREstatusRUNNINGANDlast_heartbeat_at:cutoffTime;COMMIT;它比“没有条件直接更新”好一些但仍然有几个风险。风险点为什么危险更稳妥的做法单次范围过大锁时间长影响正常读写分批处理限制每批数量无审计记录之后无法知道改了哪些数据每批保存候选 ID 与执行结果无版本校验覆盖并发修改使用状态和版本双重条件无回滚材料改错后难恢复先保存旧状态快照无异常分流所有失败都混在一起区分跳过、冲突、执行失败修复前建议额外保存“执行前快照”。CREATETABLEtask_repair_snapshotASSELECT:batchIdASbatch_id,id,status,retry_count,version,updated_atFROMtask_executionWHEREidIN(SELECTtask_idFROMtask_repair_candidateWHEREbatch_id:batchId);这张表不是为了鼓励随时回滚。更重要的是出现异常后你至少能回答哪些记录真的被修改过修改前是什么状态哪些记录只是进入候选集、最终没有变更是否需要人工恢复而不是批量反向更新。五、让 AI 先审查修复边界而不是直接生成执行脚本AI 在这类场景里最有价值的用途不是替你决定“应该改哪几条数据”。它更适合帮助你把隐含条件显式列出来。例如可以这样提问你是后端数据修复方案评审助手。 场景 任务执行表中存在部分 RUNNING 状态记录。 只有超过 30 分钟未上报心跳、未被人工挂起、属于指定任务类型的记录 才可能允许转为 RETRYABLE。 请不要直接生成全量 UPDATE。 请输出 1. 候选集筛选条件 2. 并发修改时需要保留的版本校验 3. 执行前、执行中、执行后的审计字段 4. 回滚时需要保留哪些旧值 5. 哪些记录必须转人工处理 6. 至少 8 个验证场景 7. 哪些判断必须由业务负责人确认。这类输入的关键不是让 AI 给出更复杂的 SQL。而是先让它参与“边界识别”什么可以自动修复 什么只能人工确认 什么必须保留原始证据 什么不能被重试覆盖当 ChatGPT Plus 被用于脚本复核、异常分类、测试补全和执行清单整理时工具接入准备就不只是会不会写提示词而是要把输入权限、样本范围、使用周期和异常处理方式一起纳入团队规范。团队把 AI 工具纳入日常流程时除了权限与脱敏规范也应提前确认使用规则、周期和异常处理路径需要集中核对时可参考gpt0424com六、执行完成后不要只看影响行数批量修复结束后最容易犯的错误是只确认一件事UPDATE 187 rows这个结果并不能证明修复正确。至少还要核对以下几类数据核对项需要确认的问题候选数量是否与预审数量一致实际更新数量是否存在并发冲突或状态变化跳过数量跳过原因是什么是否合理后续重试结果是否真的重新被调度异常比例是否出现某一类任务集中失败心跳恢复情况是否有仍然卡住的记录业务结果下游是否产生重复、遗漏或错序可以补一条核对 SQLSELECTstatus,COUNT(*)AStotalFROMtask_executionWHERErepair_batch_id:batchIdGROUPBYstatus;再结合调度日志、队列积压、任务耗时和失败率做复核。这里有一个非常重要的边界数据状态被改对不代表业务结果一定已经恢复。例如状态变成RETRYABLE后后续调度可能仍然失败也可能因为外部依赖不可用任务再次卡住。所以批量修复不能只停在数据库层面必须确认它是否真正恢复了后续链路。七、上线前至少覆盖这些验证场景对于这类修复方案建议至少准备下面的验证用例场景预期结果正常卡住记录被纳入候选集并成功更新正常运行记录因心跳未超时被排除人工挂起记录永远不自动更新候选集建立后状态变化因版本不一致被跳过候选集建立后心跳恢复因二次条件不满足被跳过修复任务中途异常已执行批次可追踪未执行部分不受影响同一批次重复执行不重复增加重试次数回滚演练能定位到被实际修改的记录尤其要测试“同一批次重复执行”。可以在批次表中限制唯一性例如CREATEUNIQUEINDEXuk_task_repair_onceONtask_repair_candidate(batch_id,task_id);并在执行更新时要求ANDt.repair_batch_idISNULL这样可以避免同一个批次被误跑两次后重复增加retry_count。八、结语AI 可以很快写出一条能执行的UPDATE。但数据修复真正困难的部分从来不在 SQL 语法而在于你到底要修哪一批数据哪些记录正在被其他流程处理哪些状态不能自动改变修改后是否还能追溯失败后怎样恢复是否已经验证了后续链路真的恢复。一次数据修复不应该被当作“改一条 SQL”。它更像一次小型变更发布有范围、有审计、有回滚、有验证也有不能自动化替代的人工判断。真正安全的修复不是影响行数看起来正确。而是每一条被修改的数据都能解释为什么是它、为什么现在改、改完之后如何确认结果。