【问题标题】:Can an INSERT-SELECT query be subject to race conditions?一个 INSERT-SELECT 查询可以服从竞争条件吗?
【发布时间】:2014-04-23 12:50:24
【问题描述】:

如果totals 表中不存在相应的行,我有这个查询尝试将行添加到balances 表中。查询使用 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

我对@9​​87654326@ 有一个UNIQUE 约束。我担心如果多个会话同时执行此查询,我会陷入一种竞争状况,这将导致重复的键错误。我见过很多关于这个主题的问题,但它们似乎都涉及子查询、多个查询或 pgSQL 函数。

由于我没有在我的查询中使用任何这些,它是否不受竞争条件的影响?如果不是,我该如何解决?

【问题讨论】:

  • 是的,您仍然可以得到重复键错误。出于同样的原因,当两个会话使用相同的 values 子句运行相同的 insert 语句时,您可以获得它。该语句在运行时会看到数据库的一致状态,因此即使在语句运行时提交了新行,它也不会看到任何新行。
  • 在重复键违规的情况下循环的 plpgsql 函数可以处理服务器端和默认隔离级别的竞争条件,这是 安全 一个典型的 最便宜的。该应用程序不必费心重试:stackoverflow.com/questions/15939902/…

标签: sql postgresql race-condition


【解决方案1】:

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

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

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

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

【讨论】:

  • 我实际上更喜欢重试而不是将事务级别设置为可序列化。即使有很多用户,这应该不是问题,因为可能丢失的行集是有限的,所以大多数时候这个查询应该是 NOP。
  • @Lord 它不可序列化与重试。检查更新。
  • 如果我无论如何都要重试,那我宁愿不改变事务隔离级别。我希望我能接受这两个答案...
【解决方案2】:

默认事务级别是已提交读。在这个级别可以进行幻读(参见Table 13.1)。虽然如果您更新总计,您不会在总计表中看到任何奇怪的影响,但您不会受到余额表中幻读的保护。

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

在您的情况下,多个并发语句可以看到余额丢失,并且没有什么可以阻止他们尝试插入余额并出错。

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

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE

【讨论】:

  • 我就是这么想的。感谢您确认。我在我的问题中添加了一部分:如何修复我的查询?
  • 单个语句不受幻读的影响。只有在单个事务中一个接一个地运行两个语句时,才会发生幻读。对于单个插入语句,即使基于 SELECT 语句,也不会发生幻读(该语句在运行时会看到数据库的一致状态)。 可以发生的是,当插入运行时,其他事务正在插入导致 PK 冲突的值,但这不是由幻读引起的。
  • @a_horse_with_no_name - 我知道对于重复加入同一个表的单个语句没有特殊规则(据我从文档中可以看出,它们可以编译成任意执行计划)。我同意这个问题不直接与幻读场景有关。我的意思是默认事务级别不够——它没有通过表锁保护余额表。
  • 除了将事务隔离级别设置为可序列化之外,还有其他方法吗?这听起来有点激进。
  • @Jirka 重复的自连接仍然发生在单个选择中。
猜你喜欢
  • 1970-01-01
  • 2021-11-02
  • 2021-09-27
  • 1970-01-01
  • 2013-05-06
  • 2013-04-03
  • 1970-01-01
  • 1970-01-01
  • 2017-10-07
相关资源
最近更新 更多