处理PostgreSQL中的竞争条件

时间:2014-01-30 18:16:16

标签: sql multithreading postgresql concurrency locking

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

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

我正在使用以下习语:

if [user does not exist] then [create user]

[user does not exist]的代码是:

SELECT id FROM myschema.users WHERE userId='xyz'

我测试是否返回任何行。

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

INSERT INTO myschema.users VALUES ('xyz')

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

Key (id)=(xyz) already exists

发生这种情况是因为SELECT命令没有返回任何行,然后另一个工作者创建了用户,任何我的工作者都尝试这样做,导致示例性并发错误。

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

实际上,我可以直接将if-else内容放入SQL中,但它并不能解决我的锁定问题。我假设“胜利者全部使用”范例将起作用,并且设法执行SELECT命令的第一个工作人员将拥有锁定,直到它调用COMMIT

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

2 个答案:

答案 0 :(得分:13)

您必须关心事务隔离级别。它应设置为“SERIALIZABLE”。

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

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

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

您可以通过

执行此操作
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

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

如果您想了解有关此主题的更多信息,我强烈推荐您使用此视频:http://www.youtube.com/watch?v=zz-Xbqp0g0A

答案 1 :(得分:8)

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

PERFORM pg_advisory_lock(id);
...
# do something that others must wait for
...
PERFORM pg_advisory_unlock(id);

其中idBIGINT值,我可以根据我的应用程序逻辑任意选择。

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