如果您是唯一的用户,那么查询应该没有问题。特别地,查询本身(外部查询和子查询之间)不存在竞争条件或死锁。手册:
然而,一个事务从来不会与自己发生冲突。
对于并发使用,情况可能更加复杂。您可以使用SERIALIZABLE
事务模式来确保安全:
BEGIN ISOLATION LEVEL SERIALIZABLE;
UPDATE stuff
SET computed = 'working'
WHERE id = (SELECT id FROM stuff WHERE computed IS NULL LIMIT 1)
RETURNING *
COMMIT;
在出现序列化失败的情况下,您需要准备好重试查询。
但我不完全确定这是否会过度处理。我会请@kgrittn过来……他是并发性和可串行化事务的专家。
他确实来了。 :)
两全其美
以默认事务模式READ COMMITTED
运行查询。
对于Postgres 9.5或更高版本,请使用FOR UPDATE SKIP LOCKED
。参见:
对于旧版本,请在外部UPDATE
中明确重新检查条件computed IS NULL
:
UPDATE stuff
SET computed = 'working'
WHERE id = (SELECT id FROM stuff WHERE computed IS NULL LIMIT 1)
AND computed IS NULL;
正如@kgrittn在他的答案评论中建议的那样,在(不太可能的)情况下,此查询可能会空转而没有做任何事情。
因此,它将在事务模式SERIALIZABLE
下起作用,您将不得不重试-只是没有性能损失。
唯一的问题是:虽然冲突很不可能,因为机会很短暂,但在负载高的情况下可能会发生。您无法确定是否最后还有更多行。
如果这并不重要(就像在您的情况下),那么您已经完成了。
否则,要绝对确定,请在获取空结果后再启动一次显式锁定查询。如果这也为空,则完成。如果没有,请继续。
在plpgsql中,它可能看起来像这样:
LOOP
UPDATE stuff
SET computed = 'working'
WHERE id = (SELECT id FROM stuff WHERE computed IS NULL
LIMIT 1 FOR UPDATE SKIP LOCKED); -- pg 9.5+
-- WHERE id = (SELECT id FROM stuff WHERE computed IS NULL LIMIT 1)
-- AND computed IS NULL; -- pg 9.4-
CONTINUE WHEN FOUND; -- continue outside loop, may be a nested loop
UPDATE stuff
SET computed = 'working'
WHERE id = (SELECT id FROM stuff WHERE computed IS NULL
LIMIT 1 FOR UPDATE);
EXIT WHEN NOT FOUND; -- exit function (end)
END LOOP;
这应该给您最好的两全其美:性能和可靠性。