在可重复读事务中使用SELECT ... FOR UPDATE SKIP LOCKED

12

我在我的PostgreSQL 10.5数据库中有以下语句,我在一个repeatable read事务中执行:

delete from task
  where task.task_id = (
    select task.task_id
    from task
    order by task.created_at asc
    limit 1
    for update skip locked
  )
  returning
    task.task_id,
    task.created_at

不幸的是,当我运行它时,有时会出现:

[67] ERROR:  could not serialize access due to concurrent update
[67] STATEMENT:  delete from task
  where task.task_id = (
    select task.task_id
    from task
    order by task.created_at asc
    limit $1
    for update skip locked
  )
  returning
    task.task_id,
    task.created_at

这意味着交易回滚是因为其他交易在此期间修改了该记录。 (我想是这样吧?)

我不太理解这个。不同的交易如何能够修改一个使用“for update skip locked”选择并删除的记录?

1个回答

18

这段来自手册的引用恰好讨论了您的情况:准确地说:

UPDATEDELETESELECT FOR UPDATESELECT FOR SHARE命令在搜索目标行时与SELECT相同:它们只会找到从事务启动时间开始已提交的目标行。然而,这样的目标行可能已经被另一个并发事务更新(或删除或锁定)。在这种情况下,可重复读事务将等待第一个更新事务提交或回滚(如果它仍在进行中)。如果第一个更新者回滚,则其效果将被取消,可重复读事务可以继续更新最初找到的行。但是,如果第一个更新者提交了(并且实际上更新或删除了行,而不仅仅是锁定),则可重复读事务将以消息形式回滚。

ERROR:  could not serialize access due to concurrent update
意思是,由于先前已有并发的写访问,导致你的交易无法开始锁定该行。即使使用了SKIP LOCKED也无法完全避免此问题,因为可能已经没有要跳过的锁了,如果该行已经被更改(并且更改已提交-因此锁定已释放)自交易启动以来,则仍会遇到序列化失败。
使用默认的READ COMMITTED事务隔离级别应该能够正常工作。相关:Postgres UPDATE…LIMIT 1

谢谢您的回复!我明白了,所以在锁定之前,但事务开始后,不同的客户端设法删除了该行。我想要使用“可重复读”事务隔离的原因是因为在同一事务中,我将一行插入到第二个表中。如果使用“读取已提交”,这样做仍然安全吗? - Ynv
@Ynv:是的,如果存在已提交的不同事务进行了删除或更新操作,那么使用READ COMMITTED依然是安全的。但这取决于具体的需求。安全性针对什么?我建议您开一个新的问题并定义相关细节。(如果这个问题得到了恰当的回答,请考虑接受它。) - Erwin Brandstetter

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接