生产作业记录导入双重踩坑:重复数据乱增+历史状态被覆盖,我们是怎么修复的?
生产作业记录导入双重踩坑:重复数据乱增+历史状态被覆盖,我们是怎么修复的?
上周车间运维找过来,说生产作业记录导入功能出了怪事:同一条订单的作业记录导两遍,数据库里多出两条一模一样的记录;更离谱的是,之前已经炉甩(报废)的工序,导一次文件又凭空冒出来;还有已经完成的工序,操作员填的检验结果、完成时间,全被导入文件里的空值盖掉了,当天的生产报表整个算错。
排查下来,导入逻辑其实是同时踩了两个生产类功能都容易犯的坑:幂等性缺失,加上状态保护缺失。
先看幂等键。原来的判断逻辑是"先查订单号+炉号+流节号是否存在,有就更新、没有就插入",但只用了这三个字段,没把"支号"算进去。比如某订单炉号A、流节号B下面有支号1和支号2,老逻辑会把支号2的数据错盖到支号1的记录上;而完全相同的记录重复导入时,又因为查询逻辑的漏洞反而插了新数据,成了重复。
再看全量覆盖。导入时不管工艺当前是什么状态,直接把文件里的所有字段整行写进库。工艺已经被软删、炉甩或者硬删了,导入会把删除标记清掉,等于把废弃工艺又救活;工艺已经完成,文件里没填状态、时间、结果,就把库里已经写对的值覆盖成空——之前车间导过一次含已完成工序的文件,10条已完成工序的完成时间、检验结果全被盖成空,当天的产能统计直接差了20%。
两处都补。先升级幂等判断,把唯一键从三个字段扩到四个:
// 构建唯一键:支号为空时为空字符串,不纳入拼接
String uniqueKey = String.join("|", orderNo, furnaceNo, flowNo, StringUtils.isEmpty(branchNo) ? "" : branchNo);
// 查询是否存在相同唯一键的有效记录
ProductionRecord existRecord = recordMapper.selectByUniqueKey(uniqueKey);
if (existRecord != null) {
// 仅更新基础字段,不新增记录
updateBaseInfo(existRecord, importData);
} else {
// 新增记录
insertNewRecord(importData);
}
这条规则完全对齐业务:支号为空时,同炉同流节只能有一条;支号不为空时,同炉同流节同支号才能算同一条。重复导入不再插新记录,只更新物料编码、计划数量这类基础信息。
再补一层状态保护:更新工艺列表前,先按现有状态过滤。已经是软删、炉甩、硬删、已完成这些终结状态的,直接跳过,不恢复废弃工艺、也不覆盖已经写好的状态/时间/结果;只有待生产、生产中的工艺,才允许用文件里的基础字段去更新,原有的状态、时间戳、操作结果一律冻结不动。
上线前踩的两个小坑也值得记一下。一是初期没处理好支号为空,部分没支号的订单重复导入还是出重复,后来把唯一键拼接改成支号空时用空字符串占位才解决;二是第一次上线忘了把"已完成"加进终结状态过滤,导致已完成的工序被文件覆盖,补进列表后才彻底堵住。
上线前我跑了三类核心场景验证:完全相同的数据重复导入只更新基础信息、不新增;导入含已删除工艺的文件不会复活被删数据;导入含已完成工序的文件不会盖掉历史状态和结果。连续跑了一周导入任务,再没出过数据异常。
做生产类导入,这两点我现在会当成硬规矩:导入接口必须做完整幂等,唯一键要把能标识业务重复的字段都覆盖全,重复导入优先走更新;状态、时间、操作结果这类写入后不能乱改的字段,绝不做全量覆盖,导入前先过滤掉终结状态的记录,只动生命周期内的数据。

浙公网安备 33010602011771号