You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何避免PostgreSQL并发插入时的唯一索引冲突问题?

为什么带NOT EXISTS的INSERT仍会触发唯一索引冲突?

PostgreSQL默认事务隔离级别是READ COMMITTED,在这个级别下,事务只能读取已提交的数据。当双击按钮触发两个并发请求时:

  • 事务A执行NOT EXISTS检查,此时数据库无对应(event_id, person_id)记录,通过检查后准备插入但未提交;
  • 同时事务B执行相同的NOT EXISTS检查,由于事务A的插入未提交,事务B看不到这条数据,同样通过检查并准备插入;
  • 事务A先提交后,事务B再提交时就会触发唯一索引冲突——因为此时数据库已存在该记录。

本质是NOT EXISTS检查与插入操作并非原子执行,并发场景下会出现“检查-插入”的竞态条件。

架构层面的解决方案

1. 用PostgreSQL 9.5原生INSERT ... ON CONFLICT语法(最推荐)

Postgres 9.5刚好支持该语法,让数据库原子性处理冲突,彻底规避竞态:

INSERT INTO production.event_attendee(event_id, person_id)
VALUES (?, ?)
ON CONFLICT (event_id, person_id) DO NOTHING;

逻辑是:尝试插入记录,若触发(event_id, person_id)唯一约束冲突则不执行任何操作,整个过程是原子的。

2. 调整事务隔离级别(备选)

将事务隔离级别提升至REPEATABLE READ:

  • 在Spring Boot中可通过@Transactional(isolation = Isolation.REPEATABLE_READ)配置;
  • 该级别下事务执行NOT EXISTS后,会保持一致的数据快照,后续其他事务的插入不会干扰当前事务判断。但需注意,并发冲突时会抛出SerializationFailureException,代码中需捕获并重试事务。

3. 实现接口幂等性

从接口层面拦截重复请求:

  • 前端发起请求时生成唯一幂等键(如UUID),放在请求头或参数中;
  • 后端收到请求后,先检查幂等键是否已处理(可存储在Redis或数据库幂等日志表);
  • 若已处理直接返回成功,未处理则执行插入并标记幂等键为已处理。

4. 后端接口防重复提交

通过短时间锁拦截重复请求:

  • 以用户ID、event_id、person_id为维度,用Redis设置一个短过期时间的锁(如2秒);
  • 第一个请求进来时加锁并处理,第二个请求发现锁存在时,直接返回“操作处理中”或成功响应,避免重复执行。

内容的提问来源于stack exchange,提问作者Walker Farrow

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 03:32:46