我强烈建议您研究已经解决此问题的工具,例如 PGQ。排队比你想象的要难得多。这不是你想重新发明的轮子。
并发很难
米海的回答表面上看还不错,但在并发操作中却有些落伍了。
两个并发的 UPDATE 可以选择同一行(在他的示例中为 used_flag = FALSE)。其中一个将获得锁并继续。另一个将等到第一次运行并提交。当提交发生时,第二次更新将获得锁,重新检查它的条件,找到不再匹配的行,并且什么也不做。因此,除了一组并发更新中的一个之外,所有并发更新都可能返回一个空集。
在READ COMMITTED 模式下,您仍然可以获得不错的结果,大致相当于单个会话在UPDATE 上连续循环。在SERIALIZABLE 模式下,它将无可救药地失败。试试看;这是设置:
CREATE TABLE paths (
used_flag boolean not null default 'f',
when_entered timestamptz not null default current_timestamp,
data text not null
);
INSERT INTO paths (data) VALUES
('aa'),('bb'),('cc'),('dd');
这是演示。尝试三个并发会话,逐步进行。在 READ COMMITTED 中执行一次,然后在所有会话 SERIALIZABLE 中使用 BEGIN ISOLATION LEVEL SERIALIZABLE 而不是普通的 BEGIN。比较结果。
SESSION 1 SESSION2 SESSION 3
BEGIN;
BEGIN;
UPDATE paths
SET used_flag = TRUE
WHERE used_flag = FALSE
RETURNING data;
BEGIN;
INSERT INTO
paths(data)
VALUES
('ee'),('ff');
COMMIT;
UPDATE paths
SET used_flag = TRUE
WHERE used_flag = FALSE
RETURNING data;
BEGIN;
INSERT INTO
paths(data)
VALUES
('gg'),('hh');
COMMIT;
COMMIT;
在READ COMMITTED 中,第一个 UPDATE 成功并产生四行。第二个生成剩余的两个ee 和ff,它们在第一次更新运行后插入并提交。 gg 和 hh 不会被第二次更新返回,即使它在它们提交后实际执行,因为它已经选择了它的行并且在它们被插入时正在等待锁定。
在SERIALIZABLE 隔离中,第一次更新成功并产生四行。第二个以ERROR: could not serialize access due to concurrent update 失败。在这种情况下,SERIALIZABLE 隔离不会帮助您,它只会改变故障的性质。
如果没有显式事务,当UPDATEs 并发运行时也会发生同样的事情。如果您使用显式事务,那么演示会更容易,而无需摆弄时间。
只选择一行怎么样?
如上所述,系统工作正常,但如果您只想获取最旧的行怎么办?因为UPDATE 在阻塞锁之前选择了它要操作的行,所以您会发现在任何给定的事务集中只有一个UPDATE 会返回结果。
你会想到以下技巧:
UPDATE paths
SET used_flag = TRUE
WHERE entry_id = (
SELECT entry_id
FROM paths
WHERE used_flag = FALSE
ORDER BY when_entered
LIMIT 1
)
AND used_flag = FALSE
RETURNING data;
或
UPDATE paths
SET used_flag = TRUE
WHERE entry_id = (
SELECT min(entry_id)
FROM paths
WHERE used_flag = FALSE
)
AND used_flag = FALSE
RETURNING data;
但这些不会按您的预期工作;当同时运行时,两者都会选择相同的目标行。一个会继续,一个会阻塞锁直到第一次提交,然后继续并返回一个空结果。如果没有第二个AND used_flag = FALSE,我认为他们甚至可以返回重复项!在上面的演示paths 表中添加entry_id SERIAL PRIMARY KEY 列后尝试。为了让他们参加比赛,只需在第三节 LOCK TABLE paths 即可;请参阅我在以下链接中提供的示例。
我在in another answer 和我对can multiple threads cause duplicate updates on a constrained set 的回复中写过这些问题。
说真的,去看看 PGQ。它已经为你解决了这个问题。