ldr*_*drg 3 python sql postgresql synchronization race-condition
我有一个分布式任务队列,其任务如下所示:
# creates a uniquely-named file
new_path = do_work()
old_path = database.query('select old path')
unlink(old_path)
database.query('insert new path')
Run Code Online (Sandbox Code Playgroud)
这里有一个竞争条件:如果任务队列软件在同一时间引发其中两个任务,它们将从old_path数据库中获得相同的数据,并且竞赛失败者的unlink调用失败(孤立失败者的新路径从未来取消链接).
有没有办法构建这个来绕过这场比赛?如果需要,我可以从当前的设计中抛弃任何东西.具体来说,我正在使用PostgreSQL,Python和Celery.我知道我可以使用表格范围的锁定/更改psycopg2的事务级别到SERIALIZABLE,但我不确定是否可以避免这种竞争条件.表级锁定也意味着我必须为每个附加任务引入一个新表(因为没有它们相互阻塞),这听起来不太吸引人.
我强烈建议你研究已经解决了这个问题的工具,比如PGQ.排队比你想象的要困难.这不是你要重新发明的轮子.
Mihai的回答看起来很好,但在并发操作中有所下降.
两个并发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');
Run Code Online (Sandbox Code Playgroud)
这是演示.尝试使用三个并发会话,一步一步地进行.在READ COMMITTED中执行一次,然后使用所有会话再次SERIALIZABLE使用BEGIN ISOLATION LEVEL SERIALIZABLE而不是plain 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;
Run Code Online (Sandbox Code Playgroud)
在READ COMMITTED第一个UPDATE成功并生成四行.第二个产生剩下的两个ee,ff并在第一次更新运行后插入并提交.gg并且hh不会被第二次更新返回,即使它在提交后实际执行,因为它已经选择了它的行并且在它们被插入时等待锁定.
在SERIALIZABLE隔离第一更新成功并产生四行.第二个失败了ERROR: could not serialize access due to concurrent update.在这种情况下,SERIALIZABLE隔离不会帮助你,它只会改变失败的性质.
没有显式事务,当UPDATE并发运行时会发生同样的事情.如果您使用显式事务,那么在不摆弄时间的情况下演示会更容易.
如上所述系统工作正常,但如果你想只获得最老的行怎么办?因为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;
Run Code Online (Sandbox Code Playgroud)
要么
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;
Run Code Online (Sandbox Code Playgroud)
但这些不符合您的预期; 当并发运行时,两者都将选择相同的目标行.一个将继续,一个将阻止锁定直到第一个提交,然后继续并返回一个空的结果.没有第二个AND used_flag = FALSE我认为他们甚至可以返回重复!将entry_id SERIAL PRIMARY KEY列添加到paths上面的演示表后尝试使用它.让他们参加比赛,就LOCK TABLE paths在第三场比赛中; 请参阅以下链接中给出的示例.
我在另一个答案中写了这些问题,在我的回答中,多个线程可以在约束集上引起重复更新.
说真的,去看看PGQ.它已经为你解决了这个问题.
| 归档时间: |
|
| 查看次数: |
1281 次 |
| 最近记录: |