gpt4 book ai didi

PostgreSQl 在创建新实体时正确锁定

转载 作者:行者123 更新时间:2023-12-04 14:11:43 25 4
gpt4 key购买 nike

我有两个并发事务,它们检查是否存在适当的 PostgreSQL 表记录,如果不存在 - 尝试插入一个新记录。

我有以下 Spring 数据存储库方法:

@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_idtask_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 (?,...)

最佳答案

@Lock(LockModeType.PESSIMISTIC_WRITE)
TaskApplication findByUserAndTask(User user, Task task);

将仅在查询返回的实体上获得悲观锁(行级锁)。在结果集为空的情况下,不会获取锁,并且 findByUserAndTask 不会阻止事务。

有几种方法可以处理并发插入:

  1. 使用唯一索引来防止添加重复项并根据您的应用程序需求处理异常
  2. 如果您的数据库支持,请在要插入数据的表上获取表级锁。 JPA 不支持。
  3. 在新实体和专用于存储锁的表上使用行级锁来模拟表级锁。这个新表应该为您希望在插入时获得悲观锁的每个表都有一行
    public enum EntityType {
TASK_APPLICATION
}
    @Getter
@Entity
public class TableLock {
@Id
private Long id

@Enumerated(String)
private EntityType entityType;
}
    public interface EntityTypeRepository extends Repository<TableLock, Long> {

@Lock(LockModeType.PESSIMISTIC_WRITE)
TableLock findByEntityType(EntityType entityType);
}

有了这样的设置,你只需要获取锁:

@Transactional
public TaskApplication createIfNotExists(User user, Task task) {
TaskApplication taskApplication = taskApplicationRepository.findByUserAndTask(user, task);

if (taskApplication == null) {
findByEntityType(EntityType.TASK_APPLICATION);
taskApplication = taskApplicationRepository.findByUserAndTask(user, task);

if (taskApplication == null) {
taskApplication = create(user, task);
}
}

return taskApplication;
}

对于大多数情况,第一种方法(唯一索引)是最好且最有效的。获取表锁( native /模拟)很繁重,应谨慎使用。

I'm not sure about PostgreSQL, but typically it should block the execution based on unique index in case of no record.

索引的存在不会影响 select/insert 语句是否阻塞。这种行为是由悲观锁控制的。

关于PostgreSQl 在创建新实体时正确锁定,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63768433/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com