并发执行SELECT与INSERT时是否会引发重复插入的竞态条件?
并发场景下非唯一索引字段的重复插入问题解答
结论
肯定会出现名称为“john”的数据被重复插入的情况。
原因
你这段代码是典型的「先查询后插入」逻辑,再加上name字段是非唯一索引,并发执行时完全会触发竞态条件:
- 进程A查询数据库,没找到"john",准备执行插入
- 进程B在进程A的插入操作还未提交到数据库前,也执行查询"john"的操作,同样返回空结果,进入插入分支
- 最终两个进程都完成插入,数据库中会出现多条
name为"john"的数据
另外,方法上的@Transactional无法阻止这种情况——默认的读已提交隔离级别仅能防止脏读,无法让进程B看到进程A未提交的插入操作,因此两个进程的查询结果都是空,都会执行插入逻辑。
解决办法
1. 给name添加唯一约束(最推荐)
直接在数据库的name字段上创建唯一索引,或者在实体类中通过@Column(unique = true)标注。这样即便多个进程同时执行插入,数据库会直接抛出唯一约束冲突的异常,从根源上杜绝重复数据。
2. 使用数据库锁拦截并发查询
在查询阶段就锁定相关数据,确保同一时间只有一个进程能获取查询结果:
比如用JPA的悲观锁注解改造查询方法:
@Lock(LockModeType.PESSIMISTIC_WRITE) @Query("SELECT t FROM Test t WHERE t.name = :name") Test findByNameForUpdate(@Param("name") String name);
进程A查询时会锁住对应数据,进程B必须等待A的事务提交后才能执行查询,自然不会触发重复插入。
3. 分布式锁(跨服务进程场景)
如果是多个独立的服务进程并发执行(非同一JVM内的线程),可以借助Redis或ZooKeeper实现分布式锁,保证同一时间只有一个进程能执行「查询-插入」的完整逻辑。
内容的提问来源于stack exchange,提问作者Tano
相关产品推荐
相关产品推荐

