【发布时间】:2020-12-25 08:03:36
【问题描述】:
我有两个并发事务,它们检查是否存在适当的 PostgreSQL 表记录,如果不存在 - 尝试插入一个新记录。
我有以下 Spring Data 存储库方法:
@Lock(LockModeType.PESSIMISTIC_WRITE)
TaskApplication findByUserAndTask(User user, Task task);
如您所见,我在此处添加了@Lock(LockModeType.PESSIMISTIC_WRITE)。在我的服务方法中,我检查实体是否存在,如果不存在,则创建一个新的:
@Transactional
public TaskApplication createIfNotExists(User user, Task task) {
TaskApplication taskApplication = taskApplicationRepository.findByUserAndTask(user, task);
if (taskApplication == null) {
taskApplication = create(user, task);
}
}
我还在tasks_applications (user_id, task_id) 字段上添加了唯一约束。
ALTER TABLE public.task_applications
ADD CONSTRAINT "task_applications-user_id_task_id_unique"
UNIQUE (user_id, task_id)
自动创建对应的唯一索引:
CREATE UNIQUE INDEX "task_applications-user_id_task_id_unique"
ON public.task_applications
USING btree (user_id, task_id)
不幸的是,如果两个并发事务具有相同的user_id 和task_id,第二个总是会失败,并出现以下异常:
Caused by: org.postgresql.util.PSQLException: ERROR: duplicate key value violates unique constraint "task_applications-user_id_task_id_unique"
Key (user_id, task_id)=(1, 1) already exists.
我做错了什么以及如何解决它以便能够在我的服务方法中处理这种情况?
更新
我不明白为什么在提交或回滚第一个事务之前,以下方法不会阻止第二个事务的执行:
TaskApplication taskApplication = taskApplicationRepository.findByUserAndTask(user, task);
我不确定 PostgreSQL,但通常它应该根据唯一索引阻止执行在没有记录的情况下。
如何实现?
更新 2
执行过程中产生的SQL命令序列:
select * from task_applications taskapplic0_ where taskapplic0_.user_id=? and taskapplic0_.task_id=? for update of taskapplic0_
insert into task_applications values (?,...)
【问题讨论】:
-
使用
INSERT ... ON CONFLICT。 -
谢谢,但请查看我更新的问题
-
我看到了更新,但不明白它的相关性。
-
它的相关性如下 - 我无法在我的服务方法上处理所描述的问题。同步锁机制不起作用。
-
我使用
Lock(LockModeType.PESSIMISTIC_WRITE)超过findByUserAndTask method。据我了解,它应该阻止事务#2 的执行,直到事务#1 被提交或回滚。为了存档它,它应该使用task_applications-user_id_task_id_unique索引作为信号量,以防尝试创建新实体。但它不起作用。
标签: postgresql spring-boot spring-data-jpa transactions spring-transactions