处理PostgreSQL中的竞争条件

Tre*_*reg 11 sql postgresql concurrency multithreading locking

我有几个工作者,每个工作者都拥有自己与PostgreSQL的连接.工人们操纵着不同的桌子.

工作人员处理来自系统外部的并行请求.正在访问的表之一是用户表.当一些信息出现时,我首先需要确保表中的用户有一条记录.如果没有记录,我希望首先创建一个.

我正在使用以下成语:

if [user does not exist] then [create user]
Run Code Online (Sandbox Code Playgroud)

代码[user does not exist]是:

SELECT id FROM myschema.users WHERE userId='xyz'
Run Code Online (Sandbox Code Playgroud)

我测试是否返回任何行.

(简化)代码[create user]是:

INSERT INTO myschema.users VALUES ('xyz')
Run Code Online (Sandbox Code Playgroud)

当我的系统处理有关同一用户的不同信息的并行流时,我经常会收到PostgreSQL错误:

Key (id)=(xyz) already exists
Run Code Online (Sandbox Code Playgroud)

之所以发生这种情况是因为该SELECT命令不返回任何行,然后另一个工作程序创建用户,任何我的工作程序都尝试执行相同操作,从而导致示例性并发错

根据PostgreSQL文档,默认情况下,每当我隐式启动一个事务时,只要我不提交它,表就会被锁定.我没有使用自动提交,我只在块中提交事务,例如在整个if-else块之后.

实际上,我可以if-else直接将这些东西放入SQL中,但它并不能解决我的锁定问题.我假设"赢家全部使用"范例将起作用,并且设法执行SELECT命令的第一个工作人员将拥有锁,直到它调用COMMIT.

我在SO上已经阅读了很多不同的主题,但我仍然不确定什么是正确的解决方案.我应该使用显式锁定表,因为隐式锁定不起作用?我怎样才能确保只有一个工人拥有一张桌子?

maj*_*aja 13

您必须关心事务隔离级别.它应该设置为" SERIALIZABLE".

原因是Phantom Reads- 事务不会锁定整个表,而只锁定事务已读取的行.

因此,如果另一个事务插入新数据,它们尚未锁定,则会出现错误.

Serializable通过阻止所有其他事务来避免这种情况,直到完成此事务.

你可以通过这样做

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
Run Code Online (Sandbox Code Playgroud)

文件:http://www.postgresql.org/docs/9.1/static/transaction-iso.html

如果您想了解有关此主题的更多信息,我强烈建议您观看此视频:http://www.youtube.com/watch?v = zzz-Xbqp0g0A

  • 虽然“SERIALIZABLE”事务隔离是一个干净的解决方案,但它也相当“昂贵”。在这种情况下,您需要为序列化失败做好准备并重试。对于“插入或选择”问题,这是更便宜(而且也干净)的替代方案:http://stackoverflow.com/questions/15939902/is-select-or-insert-in-a-function-prone-to-race -条件/15950324#15950324 (3认同)

Tre*_*reg 8

实际上,经过ISOLATION LEVEL SERIALIZABLE@maja提出的一些混乱之后,我发现了更简单的机制:

PERFORM pg_advisory_lock(id);
...
# do something that others must wait for
...
PERFORM pg_advisory_unlock(id);
Run Code Online (Sandbox Code Playgroud)

这里id是一个BIGINT我可以根据我的应用程序的逻辑任意选择值.

这给了我正在寻找的力量和灵活性.