INSERT-SELECT查询是否可以受竞争条件的影响?

时间:2014-04-23 12:50:24

标签: sql postgresql race-condition

如果balances表中没有对应的行,我有这个查询尝试向totals表添加行。使用PostgreSQL上的默认隔离级别在事务中运行查询。

INSERT INTO balances (account_id, currency, amount)
SELECT t.account_id, t.currency, 0
FROM balances AS b
RIGHT OUTER JOIN totals USING (account_id, currency) AS t
WHERE b.id IS NULL

我对UNIQUE有一个balances (accountId, currency)约束。我担心如果多个会话同时执行此查询,我将陷入竞争状态,导致重复键错误。我已经看过很多关于这个问题的问题,但它们似乎都涉及子查询,多个查询或pgSQL函数。

由于我没有在我的查询中使用任何这些,它是否没有竞争条件?如果不是我该怎么办呢?

2 个答案:

答案 0 :(得分:3)

是的,它会因duplicate key value violates unique constraint错误而失败。我所做的是将插入代码放在try/except块中,当抛出异常时,我抓住它并重试。那么简单。除非应用程序拥有大量用户,否则它将完美运行。

在您的查询中,默认隔离级别就足够了,因为它是一个插入语句,并且不存在幻像读取的风险。

请注意,即使将隔离级别设置为可序列化,try / except块也是不可避免的。 From the manual about serializable:

  

与可重复读取级别一样,使用此级别的应用程序必须准备好因序列化失败而重试事务

答案 1 :(得分:1)

默认事务级别为Read Committed。可以在此级别进行幻像读取(请参阅Table 13.1)。虽然您可以保护您不会在总计表中看到任何奇怪的效果,但是您无法更新总计表中的幻像读取。

在查看类似于您的单个查询时,可以解释这意味着什么,尝试外连接两次(并且只查询,不插入任何内容)。缺少余额这一事实并不能保证在余额表的两个“偷看”之间保持不变。当同一笔交易第一次出现时,突然出现的余额并不存在,称为“幻读”。

在您的情况下,几个并发语句可以看到缺少余额,没有任何东西阻止他们尝试插入和错误输出。

要排除幻像读取(并修复查询),您需要在运行查询之前在SERIALIZABLE隔离级别执行:

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE