UPDATE orders SET status=?, version=version+1
WHERE id=? AND version < ?;
-- 影响行数为 0 说明是过期消息 直接丢弃

订单状态先写成 shipped 再被改回 paid,updated_at 差了 3 分钟,两条消息的 msg_id 顺序和写入顺序相反。先别急着动参数,把报错原文和时间点记下来,顺序反了容易白折腾。

原因不复杂,第一条消息处理失败后进入重试队列,退避 60 秒;第二条正常消费先写了库。重试成功后又把旧状态覆盖回去。

「重试消息乱序」确认了原因之后,改动尽量只碰那一处,顺手改的东西后面最容易出问题。改动写完就同步给同事,下一个人踩同一个坑,成本还是你的。

验证用真实数据走一遍,测试数据太干净,边界情况根本碰不到。

处理上按这个顺序:消费端用版本号或状态机校验 不允许回退到更早的状态;同一业务键的消息投到同一分区或队列 保证串行;重试时保留原始入队时间 消费端丢弃比当前记录更旧的变更;对状态回退打日志并计数 确认没有新的覆盖。

改完只确认一件事:连续压测 1000 条乱序消息 订单状态只向前推进 版本号单调递增 回退计数为 0。

作者 9823189