ale*_*oid 2 postgresql transactions spring-transactions spring-data-jpa spring-boot
我有两个并发事务,它们检查是否存在适当的 PostgreSQL 表记录,如果不存在,则尝试插入一个新记录。
我有以下 Spring 数据存储库方法:
@Lock(LockModeType.PESSIMISTIC_WRITE)
TaskApplication findByUserAndTask(User user, Task task);
Run Code Online (Sandbox Code Playgroud)
正如你可能看到的,我已经@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);
}
}
Run Code Online (Sandbox Code Playgroud)
我还添加了对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)
Run Code Online (Sandbox Code Playgroud)
自动创建对应的唯一索引:
CREATE UNIQUE INDEX "task_applications-user_id_task_id_unique"
ON public.task_applications
USING btree (user_id, task_id)
Run Code Online (Sandbox Code Playgroud)
不幸的是,如果两个并发事务具有相同的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.
Run Code Online (Sandbox Code Playgroud)
我做错了什么以及如何修复它以便能够在我的服务方法中处理这种情况?
更新
我不明白为什么以下方法不会阻止第二个事务的执行,直到第一个事务被提交或回滚:
TaskApplication taskApplication = taskApplicationRepository.findByUserAndTask(user, task);
Run Code Online (Sandbox Code Playgroud)
我不确定 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 (?,...)
Run Code Online (Sandbox Code Playgroud)
小智 5
@Lock(LockModeType.PESSIMISTIC_WRITE)
TaskApplication findByUserAndTask(User user, Task task);
Run Code Online (Sandbox Code Playgroud)
将仅在查询返回的实体上获得悲观锁(行级锁)。在结果集为空的情况下,不会获取锁,并且findByUserAndTask不会阻止事务。
有几种方法可以处理并发插入:
public enum EntityType {
TASK_APPLICATION
}
Run Code Online (Sandbox Code Playgroud)
@Getter
@Entity
public class TableLock {
@Id
private Long id
@Enumerated(String)
private EntityType entityType;
}
Run Code Online (Sandbox Code Playgroud)
public interface EntityTypeRepository extends Repository<TableLock, Long> {
@Lock(LockModeType.PESSIMISTIC_WRITE)
TableLock findByEntityType(EntityType entityType);
}
Run Code Online (Sandbox Code Playgroud)
有了这样的设置,你只需要获得锁:
@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;
}
Run Code Online (Sandbox Code Playgroud)
对于大多数情况,第一种方法(唯一索引)是最好且最有效的。获取表锁(本机/模拟)很繁重,应谨慎使用。
我不确定 PostgreSQL,但通常它应该在没有记录的情况下根据唯一索引阻止执行。
索引的存在不会影响 select / insert 语句是否阻塞。这种行为是由悲观锁控制的。
| 归档时间: |
|
| 查看次数: |
2878 次 |
| 最近记录: |