UPSERT并发竞争下的「幽灵ID」脏数据 前言线上遇到一类隐蔽生产故障程序运行无报错、事务正常提交却源源不断产生僵尸脏数据。当两个事务并发对同一业务单号执行UPSERT时其中一个事务会被唯一索引排他记录锁阻塞被阻塞的事务唤醒后因唯一键冲突降级执行UPDATE不会新增主表数据。依托MyBatis-Plus雪花ID策略实体提前在内存生成主键若下游直接使用这个ID创建子记录就会出现子表有数据、主表无匹配记录的「幽灵ID」脏数据。环境说明MySQL InnoDBRC、RR隔离级别均会复现业务单号建立唯一索引并发事务均使用UPSERT写入主键策略使用 IdType.ASSIGN_ID 雪花算法。完整并发执行时序1. 事务A、事务B同时处理同一业务单号 a 执行UPSERT2. MP在SQL执行前分别为两个实体在内存预生成独立雪花ID3. 事务A率先抢占唯一索引排他记录锁执行SQL事务尚未提交4. 事务B检测到锁冲突进入阻塞等待。备注RC、RR隔离级别下事务B均无法读取事务A未提交的数据因此预先查询判断业务单号 a 是否存在无法规避该并发问题5. 事务A执行commit数据成功写入数据库6. 事务B被MySQL唤醒再次校验唯一索引发现单号 a 已存在7. 事务B进入UPDATE分支仅更新已有记录不会新增主表行。幽灵ID产生核心原理雪花ID由应用内存提前生成和数据库执行结果无关。即便UPSERT最终走入更新分支没有插入新数据实体对象依旧保留预先生成的雪花ID。业务代码直接读取实体ID用于新增明细、流水等子记录。结果事务B携带的雪花ID仅存在于JVM内存数据库不存在该主键也就是幽灵ID最终滋生大量悬浮子表脏数据。补充IdType.AUTO 自增主键在UPSERT下的表现自增主键行为和雪花ID有明显区别1. 常规写法新建实体 idnull 。UPSERT进入UPDATE分支时无法回调主键实体ID维持null。只要代码增加ID非空判断就能规避脏数据2. 危险写法代码手动提前 setId() 设置占位ID。更新分支不会覆盖实体属性占位ID保留同样会生成幽灵ID。整体来看自增主键默认场景风险更低但并非绝对安全。生产级解决方案1. UPSERT执行完成后禁止直接使用实体内的主键。必须根据唯一键业务单号重新查询数据库拿到真实持久化主键2. 校验UPSERT执行影响行数int affectRows mapper.upsert(entity); // 返回1新增成功返回2冲突更新禁止使用当前实体主键关联子数据3. 针对业务唯一单号增加分布式锁保证同一单号串行执行从源头消除并发争抢4. 核心主数据业务不要无脑滥用UPSERT可拆分新增、更新逻辑。总结同一唯一键并发UPSERT时被锁阻塞的事务最终降级执行更新、不会新增主表记录。雪花算法 IdType.ASSIGN_ID 预先在内存生成主键极易诞生幽灵ID是高频风险点自增主键 IdType.AUTO 仅在手动赋值实体ID的特殊场景下会踩坑。

相关新闻

最新新闻

Unity游戏本地化实战:XUnity Auto Translator自动化翻译与配置指南

Unity游戏本地化实战:XUnity Auto Translator自动化翻译与配置指南

1. 项目概述:为什么Unity游戏本地化是门必修课?如果你是一名独立游戏开发者,或者在一个小型团队里负责技术实现,那么“本地化”这个词对你来说,可能既熟悉又陌生。熟悉的是,你知道它意味着要把游戏里的文字…

2026/8/2 4:21:13
工业边缘计算实践:基于树莓派CM4与Ignition Edge的HMI解决方案

工业边缘计算实践:基于树莓派CM4与Ignition Edge的HMI解决方案

1. 项目概述:当工业边缘计算遇上现代HMI最近在折腾一个工业现场的边缘数据采集与可视化项目,核心硬件选用了树莓派计算模块CM4驱动的reTerminal DM,而软件栈则决定上Ignition Edge。这个组合听起来可能有点“跨界”——一个是消费级硬件出身的…

2026/8/2 4:21:13
前端跨域 iframe 通信别再手写 postMessage:用 iframe-js 实现 ACK、RPC 与状态同步

前端跨域 iframe 通信别再手写 postMessage:用 iframe-js 实现 ACK、RPC 与状态同步

多年前,我因为项目中频繁遇到 iframe 跨域通信问题,写了一个小工具来封装 postMessage。后来这个工具逐渐完善,最终开源成了现在的 iframe-js。 这是我第一次认真写文章介绍这个项目。过去三年里,它从最初只能发送消息&#xff0…

2026/8/2 4:21:13
Spring Bean 的生命周期到底是什么?

Spring Bean 的生命周期到底是什么?

Bean 的生命周期,指的是一个对象在 Spring 容器中,从创建、初始化,到被使用,再到最终销毁所经历的完整过程。 这道题是面试高频题,但很多人备考时习惯去网上找帖子背图。 其实不需要死记硬背,把核心阶段理…

2026/8/2 4:21:13
【单片机毕设案例分享】基于 STM32 的 IC 卡车辆出入计时收费终端设计 嵌入式 RFID 刷卡智能停车闸道管控系统开发(016501)

【单片机毕设案例分享】基于 STM32 的 IC 卡车辆出入计时收费终端设计 嵌入式 RFID 刷卡智能停车闸道管控系统开发(016501)

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于单片机,STM32单片机,51单片机,J…

2026/8/2 4:21:13
XIAO nRF52840与MicroBlocks:图形化编程与蓝牙物联网开发实战

XIAO nRF52840与MicroBlocks:图形化编程与蓝牙物联网开发实战

1. 从“硬核编程”到“积木式创作”:为什么选择这个组合?如果你和我一样,玩过一阵子Arduino或者MicroPython,享受过那种从零开始构建项目的乐趣,但也一定经历过这样的时刻:面对一个简单的想法,比…

2026/8/2 4:16:13