如果您是唯一用户,则查询应该没问题。特别是,查询本身(外部查询和子查询之间)没有竞争条件或死锁。我引用手册here:
但是,事务永远不会与自身发生冲突。
对于并发使用,事情可能会更复杂。使用SERIALIZABLE transaction mode,您会更安全:
BEGIN ISOLATION LEVEL SERIALIZABLE;
UPDATE stuff
SET computed = 'working'
WHERE id = (SELECT id FROM stuff WHERE computed IS NULL LIMIT 1)
RETURNING *
COMMIT;
您需要为序列化失败做好准备,并在这种情况下重试查询。
但我不完全确定这是否矫枉过正。我会请@kgrittn 停下来......他是并发和可序列化事务的专家......
And he did. :)
两全其美
以默认事务模式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 中的第一个变体非常相似,您必须重试 - 只是没有性能损失。
唯一的问题:虽然由于机会之窗很小,所以冲突不太可能发生,但它可能在重负载下发生。您无法确定是否最终没有更多行了。
如果这无关紧要(就像您的情况一样),您就完成了。
如果确实如此,绝对确定,在得到一个空结果后使用explicit locking 再开始一个查询。如果这是空的,你就完成了。如果没有,请继续。
在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;
这应该给你两全其美:性能和可靠性。