AI帮我们优化批量任务时,经常会做一件看起来很合理的事:

把原来一条一条处理,改成分页、批量甚至并发执行。

速度确实变快了,但随后可能出现另一类很隐蔽的问题:

  • 明明任务全部跑完,最终数量却对不上;
  • 某些数据处理了两次;
  • 有些记录完全没被处理;
  • 第一次运行正常,第二次结果又不同;
  • 并发数越高,重复和遗漏越明显;
  • 日志没有明显报错,但业务数据就是少了一部分。

这类问题的危险之处在于:

任务“执行完成”并不代表每条数据都被正确处理了一次。

一、最常见的问题是分页过程中数据本身还在变化

假设AI把任务改成:

每次查询100条,处理完以后继续下一页。

如果使用 LIMIT + OFFSET

第一页读取第1~100条,第二页再读取第101~200条。

看起来没有问题。

但如果第一批处理过程中,某些数据状态被修改,已经不再满足原来的查询条件,剩余数据的位置就可能发生变化。

例如原本有300条“待处理”数据。

第一批处理100条以后,这100条状态变成“已完成”,从查询结果中消失。

这时候再执行:

OFFSET 100

实际上可能直接跳过新的前100条。

结果就是:

任务没有报错,但中间一批数据被漏掉了。

二、一边分页,一边修改查询条件特别危险

很多批处理都会使用类似条件:

查询 status = pending 的记录。

处理完成后,再把它改成:

status = done。

这意味着数据集本身一直在缩小。

如果继续使用固定Offset翻页,相当于:

一边改变队伍顺序,一边按照旧位置继续往后数。

所以这类任务里,代码表面完全正确,分页SQL也没有语法问题,但最终结果仍然可能缺数据。

三、没有稳定排序,也可能导致分页结果漂移

即使数据没有被删除,如果查询没有明确稳定排序,也可能出问题。

例如只写:

查询100条记录。

数据库并不保证每次都按照完全一样的顺序返回。

第一页里出现过的数据,下一次查询时可能因为执行计划、数据变化等原因跑到另一页。

如果任务依赖分页边界,就可能出现:

同一条记录重复出现,或者某条记录被跨页跳过。

所以批量处理最好保证有明确、稳定的排序字段。

例如:

按唯一ID递增处理。

四、Cursor为什么通常比Offset更适合持续批处理

相比一直使用:

OFFSET 100、OFFSET 200、OFFSET 300

更稳定的一种思路是记录:

上一批最后处理到哪个ID。

例如第一批处理到:

id = 1050

下一批直接查询:

id > 1050,继续取100条。

这种方式的核心不是“第几页”,而是:

上一次处理到了哪里。

即使前面的数据状态发生变化,也不会因为结果集缩小而把后面的记录整体向前移动。

这就是为什么很多长期运行的批处理更适合基于:

Cursor、主键或稳定游标

继续推进。

五、并发消费又会引入另一类重复问题

AI为了进一步提升速度,可能把一个Worker改成多个Worker同时处理。

例如:

Worker A查询一批待处理任务。

Worker B同时也查询一批待处理任务。

如果两边查询发生得很接近,就可能同时拿到同一条记录。

结果:

A处理一次,B又处理一次。

如果这个任务只是生成报表,可能影响不大。

但如果涉及:

  • 扣库存;
  • 发优惠券;
  • 发短信;
  • 创建订单;
  • 调第三方接口;

重复消费就可能直接造成业务事故。

六、“先查出来再改状态”也存在时间窗口

例如流程是:

查询 pending → 执行业务 → 更新为 done

两个Worker可能同时在“更新为done”之前读到同一条任务。

所以并发环境下,仅仅依赖:

我处理完以后会改状态。

并不能保证任务只执行一次。

更稳的方式通常要考虑:

  • 原子抢占;
  • 状态条件更新;
  • 唯一约束;
  • 分布式锁;
  • 队列消费确认;
  • 幂等处理。

核心目标是:

同一条任务不能同时被多个Worker认为“属于自己”。

七、为什么测试很容易发现不了?

普通测试往往只有:

  • 少量数据;
  • 单Worker;
  • 数据不会实时变化;
  • 一次执行完成就结束。

这和生产环境差别很大。

真正上线以后,可能同时存在:

  • 新数据不断插入;
  • 旧数据持续更新;
  • 多个Worker抢任务;
  • 任务失败重试;
  • 数据库响应时间变化。

于是原本在测试环境非常稳定的分页逻辑,到了生产环境才开始重复或漏数据。

所以批量任务不能只测试:

能不能跑完。

还要检查:

处理数量、唯一性和完整性。

八、怎么检查有没有重复或遗漏?

最直接的方法不是只看:

Job completed successfully。

而是增加结果校验。

例如记录:

  • 本次扫描总数;
  • 实际处理总数;
  • 成功数量;
  • 失败数量;
  • 唯一ID数量;
  • 重复ID数量;
  • 未处理剩余数量。

如果:

处理次数 = 10000

但:

唯一ID只有9800

那就说明存在重复。

如果理论应该处理10000条,最终只有9600条进入完成状态,就要继续排查遗漏。

批量任务真正需要的是:

结果对账。

九、AI优化批处理后,最好做一次完整性检查

以后让AI优化批量任务,可以明确要求:

请检查当前分页是否会因为处理过程中数据状态变化导致Offset漂移;确认排序是否稳定;评估是否应该改成基于ID或Cursor继续处理;如果存在多Worker并发,请检查任务抢占、重复消费和幂等性。最后增加处理总数、唯一ID数、失败数和剩余数校验。

这样AI关注的就不只是:

任务快了多少。

还会进一步检查:

每条数据是不是恰好处理一次。

最后

AI优化批量任务后出现数据重复或漏处理,本质上通常不是:

循环写错了。

而是:

数据集在变化,而分页和并发策略仍然假设它是静态的。

Offset分页可能因为数据移动而跳过记录,并发Worker可能因为抢占不完整而重复处理。

所以真正稳定的批任务,需要同时考虑:

稳定排序、Cursor推进、任务抢占、幂等性和结果对账。

批处理真正的正确标准不是:

任务有没有跑完。

而是:

所有应该处理的数据,是否都被准确处理,而且没有重复。


持续更新 Codex、AI编程与后端工程实战内容,更多深度内容和稳定订阅渠道欢迎搜索关注「孤狼GPT」。

Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐