一条订单,两个写入方,两条都成功的更新,一单发错地址。
晚上九点,客服改收件人,保存成功。同一分钟,发货任务回写状态,也成功。第二天包裹发出------收件人是旧地址。日志里没有一条报错。

根因就一句
两条 UPDATE 都是按主键整行写回。发货任务在九点前就读了这张单,内存里拿着旧快照;写回时把状态和它没碰过的收件人、地址一起写回。谁后提交谁赢,先提交的改动被整体覆盖。
ini
UPDATE orders SET receiver='王*', address='新地址',
status='待发货' WHERE id=1001; -- 客服:成功
UPDATE orders SET receiver='李*', address='旧地址',
status='已发货' WHERE id=1001; -- 任务:也成功
修法三行
表加 version 字段自增,更新带版本条件,看受影响行数:
ini
UPDATE orders SET status='已发货', version=version+1
WHERE id=1001 AND version=7;
-- rows == 0 就是冲突,走显式分支,不许静默
版本号随查询下发、随提交回传,前端编辑链路才闭环。
冲突后的纪律(最容易写错的地方)
拿旧数据硬重放 = 换个姿势覆盖。正确动作:重读 → 把本次意图重新应用到最新数据 → 再走一次条件更新,重试设上限。
重试之前必须重读,重试不等于重放。 服务端再进一步:写请求带数据代际,代际落后于当前版本直接拒绝这条在途旧请求。
快速自检
- 按
WHERE id=整行写回的 UPDATE 有多少条没带版本条件? - rows==0 的分支是显式处理还是被吞了?
- 缓存里的订单对象带不带 version?旧缓存会让判定失效。
验证动作:测试订单两个终端读同一版本先后提交,后提交方应收到冲突提示,前一个人的改动完好。五分钟搞定。