我确实在可序列化事务中对此进行了测试,它似乎按预期工作。但我想知道这里的保证。执行顺序是什么?我的期望是
order/skip->limit->lock
。
它会锁定多于一行的行吗?
我的实际场景: 我有一个表,其中每一行代表一个工作单元。我有多个并发工作人员,它们应该启动一个事务,通过
select ... where completed=false order by random() for update skip locked limit 1
保留一个工作单元,并在工作结束时更新包含 completed=true
列的行,然后再提交事务。如果预留返回零行或带有 SQL Error [40001]: ERROR: could not serialize access due to concurrent update
错误,则它将重新启动其事务。重点是避免在工作开始时立即失败来避免做不必要的工作。
我担心的是,即使只选择一行,是否也可能存在悬空锁?
在咨询锁文档中,它对 LIMIT 查询发出警告:
因为在执行锁定函数之前不能保证 LIMIT 被应用。这可能会导致获取应用程序未预期的一些锁,因此无法释放(直到结束会话)。从应用程序的角度来看,这样的锁将是悬空的
但是这仅适用于咨询锁而不适用于 SELECT FOR UPDATE?我找不到这样的 SELECT FOR UPDATE 的警告文档。在 select docs 上只有一个关于无序返回行的警告,这在我的情况下不是问题,因为无论如何我只是随机排序。
是的,这是特定于咨询锁的,它是使用函数获取的。
要回答这样的问题,请查看执行计划。它看起来类似于这样:
EXPLAIN (COSTS OFF)
SELECT * FROM customers
ORDER BY random()
FOR NO KEY UPDATE SKIP LOCKED
LIMIT 1;
QUERY PLAN
═════════════════════════════════════════
Limit
-> LockRows
-> Sort
Sort Key: (random())
-> Seq Scan on customers
(5 rows)
排序后,行按照排序返回的顺序被锁定,一旦
LockRows
锁定第一行并将其传递到 Limit
节点,处理就会停止。
请注意,只有当您打算删除行或修改主键或唯一键时,
FOR UPDATE
才是正确的锁。对于正常的 UPDATE
,您应该使用 FOR NO KEY UPDATE
以避免过度锁定。
您似乎使用了高于已提交读的隔离级别,因为您收到了序列化错误。请注意,这并不要求该算法正确(但当然没问题)。