竞态条件下主键约束仍报错:单条INSERT NOT EXISTS语句为何失效?
问题根因
你遇到的主键冲突本质还是并发场景下的读判断一致性问题,核心原因有两个:
- 数据库隔离级别的限制:绝大多数数据库默认的事务隔离级别是读提交(Read Committed),你写的
INSERT ... WHERE NOT EXISTS语句执行时,会先执行NOT EXISTS的子查询拿到当前事务可见的快照数据,如果两个线程的事务同时执行这条SQL,子查询执行瞬间双方的插入操作都还没提交,所以两个事务的子查询都会判定目标员工ID不存在,后续两个事务都会执行插入逻辑,自然触发主键约束冲突。 - 单条SQL不代表无并发间隙:单条DML语句只是逻辑上是一个整体,但其内部执行流程仍然是「先做读判断、再做写操作」两个步骤,读判断和写操作之间并没有自动加全局排他锁来阻塞其他同条件的写入操作,只要多个事务的读判断都通过,就都会执行写入触发冲突。
你遗漏的核心要点
- 没有做主键冲突的兜底容错:不管怎么优化插入逻辑,主键冲突的异常理论上无法100%避免,你需要在Java业务代码中主动捕获主键约束违反的SQL异常,直接将该异常当做「记录已存在,插入逻辑执行成功」处理即可,不需要向上抛出业务错误。
- 优先用数据库原生的幂等插入语法,比自行编写的
NOT EXISTS逻辑冲突概率更低:
如果你用的是Oracle,推荐用MERGE语句:
如果你用的是MySQL,推荐用MERGE INTO employee_hist t USING dual ON (t.employee_id = 1000) WHEN NOT MATCHED THEN INSERT (emp_id, description) VALUES (1000, 'bush');INSERT IGNORE或者INSERT ... ON DUPLICATE KEY UPDATE语法。 - 高并发场景下如果冲突频率过高,可以在业务层对员工ID加分布式锁,同一时间只允许一个线程执行同一个员工ID的插入操作,从源头规避并发冲突。
内容的提问来源于stack exchange,提问作者manjit
相关产品推荐
相关产品推荐

