2. 追溯码重复赋码冲突的根因分析与处置

问题定位:同一追溯码为何落到两个产品单元

追溯码重复赋码的本质是“一码一物”约束被破坏。排查时不要先猜原因,而应把三类证据按时间轴对齐:赋码流水(谁在什么时间申请/写入了该码)、设备日志(喷码/贴标设备是否重发、重试、离线补传)、数据库唯一约束记录(唯一索引冲突报错、事务回滚、幂等键命中情况)。三者交叉后,通常可归入设备重发、人工补码、系统并发三类根因之一。

冲突现象 追溯码 T20240518001 同时绑定产品单元 A 与 B 赋码流水 · 申请时间 / 操作员 / 工单 · 码段来源与分配批次 · 写入结果(成功/失败/重试) 设备日志 · 喷码/贴标指令与重发次数 · 离线缓存补传记录 · 设备时钟与心跳状态 数据库唯一约束记录 · 唯一索引冲突报错码 · 事务回滚与幂等键命中 · 两行记录的创建时间差 按时间轴对齐三类证据 比较两次写入的时间差、操作员、设备号与事务状态 ① 设备重发 同一设备号、毫秒级重复指令 重发计数 > 1,无人工操作 处置:设备侧幂等 + 指令去重 ② 人工补码 不同操作员、间隔较长 补码单未校验原码状态 处置:补码前查码 + 作废原绑定 ③ 系统并发 两事务时间差 < 10ms 唯一索引缺失或未生效 处置:唯一索引 + 乐观锁重试 去重与补偿方案 保留首次有效绑定 → 作废重复绑定 → 补发新码并回写追溯链 → 记录处置审计

用赋码流水与设备日志区分三类根因

先取该追溯码在赋码流水表中的全部记录,按 created_at 升序排列,重点看四个字段:operator_id、device_id、work_order、write_result。再取设备日志中同一码的喷码指令记录,比对 cmd_seq(指令序号)与 retry_count(重发次数)。判断规则如下:

  • 设备重发:两条流水来自同一 device_id,时间差在毫秒级,设备日志中 retry_count ≥ 1,且无人工补码单。说明设备在未收到确认时自动重发,而服务端未做幂等。
  • 人工补码:两条流水 operator_id 不同,时间差通常为分钟到小时级,且存在补码申请单。说明补码时未校验原码是否已绑定有效产品单元。
  • 系统并发:两条流水时间差极小(如 < 10ms),来自不同服务实例或线程,数据库唯一约束记录中出现重复键冲突但被吞掉,或唯一索引根本未建立。说明“先查后写”存在竞态窗口。

数据库唯一约束记录的关键读法

唯一约束记录是判定并发问题的直接证据。检查 trace_code_binding 表是否在 trace_code 上建立了唯一索引;若已建立,冲突时应抛出重复键错误(如 MySQL 的 1062),并可在应用日志中看到对应事务回滚。若日志中只有“查询无记录→插入成功”两条记录而无冲突报错,则说明唯一索引缺失或写入走了不同分片,属于结构性缺陷。

-- 检查唯一索引是否存在
SHOW INDEX FROM trace_code_binding WHERE Column_name = 'trace_code';

-- 定位重复绑定的两条记录
SELECT id, trace_code, product_unit_id, operator_id, device_id,
       created_at, status
FROM trace_code_binding
WHERE trace_code = 'T20240518001'
ORDER BY created_at;

去重与补偿方案设计

处置原则是“保留首次有效绑定、作废重复绑定、补发新码、回写追溯链、留审计”。具体动作:

  1. 以最早一条 status='active' 的绑定为准,保留其 product_unit_id;其余重复绑定置为 void,并记录 void_reason='duplicate_binding'。
  2. 对被作废绑定所对应的产品单元,重新申请新追溯码并补打标签,确保实物与系统一致。
  3. 在赋码服务入口增加幂等键(如 device_id + cmd_seq),并在数据库层强制唯一索引,双保险防止再次并发写入。
  4. 补码流程增加“原码状态校验”:若原码已绑定有效单元,必须先作废再补码,禁止直接覆盖。
验证处置是否成功:再次查询该追溯码,应只返回一条 active 绑定;被作废记录保留可审计;新补发的码能正常关联到原产品单元且追溯链完整。
不要直接物理删除重复绑定记录。追溯数据要求可审计,删除会破坏排障证据链,正确做法是状态置为作废并保留时间戳与操作人。