当前位置: 首页 > 知识库问答 >
问题:

多个线程是否会在受约束集上导致重复更新?

南门焱
2023-03-14

在postgres中,如果我运行以下语句

update table set col = 1 where col = 2

在默认的 READ COMMITT 隔离级别中,我可以保证从多个并发会话中:

  1. 在单个匹配的情况下,只有1个线程将获得1的ROWCOUNT(意味着只有一个线程写入)
  2. 在多匹配的情况下,只有1个线程将获得ROWCOUNT

共有1个答案

富勇军
2023-03-14

您声明的保证适用于这个简单的情况,但不一定适用于稍微复杂一点的查询。例子见答案末尾。

假设col1是唯一的,只有一个值“2”,或者具有稳定的排序,因此每个UPDATE都以相同的顺序匹配相同的行:

该查询将会发生的情况是,线程将会找到col=2的行,并且所有线程都尝试在该元组上获取一个写锁。其中一个会成功。其他线程将阻塞等待第一个线程的事务提交。

第一个tx将写入、提交并返回1的行计数。提交将释放锁。

其他tx将再次尝试获取锁。他们会一个接一个地成功。每笔交易将依次经过以下过程:

    < li >获取有争议元组的写锁。 < li >获得锁定后,重新检查< code>WHERE col=2条件。 < li >重新检查将显示条件不再匹配,因此< code>UPDATE将跳过该行。 < Li > < code > UPDATE 没有其他行,因此它将报告零行更新。 < li >提交,为下一个试图获取锁的tx释放锁。

在这个简单的例子中,行级锁定和条件重新检查有效地序列化了更新。在更复杂的情况下,没有那么多。

你可以很容易地证明这一点。比如说四个psql会话。首先,用< code>BEGIN锁定表;锁表测试;*在其余的会话中运行相同的< code>UPDATE,它们将阻塞表级锁。现在通过< code >提交您的第一个会话来释放锁定。看他们比赛。只有一个会报告行数为1,其他的会报告行数为0。这很容易自动化和脚本化,以便重复和扩展到更多的连接/线程。

要了解更多信息,请阅读 PostgreSQL 并发问题的第 11 页并发写入规则,然后阅读该演示的其余部分。

正如Kevin在注释中指出的那样,如果col不是唯一的,因此您可以匹配多行,那么UPDATE的不同执行可能会获得不同的顺序。如果他们选择不同的计划(比如说一个是通过准备和执行,另一个是直接的,或者你搞砸了enable_GUC),或者如果他们都使用不稳定的相等值,就会发生这种情况。如果他们以不同的顺序获取行,则 tx1 将锁定一个元组,tx2 将锁定另一个元组,然后他们每个人都将尝试在彼此已锁定的元组上获取锁定。PostgreSQL将中止其中一个,并出现死锁异常。这是为什么所有数据库代码都应始终准备好重试事务的另一个很好的原因。

如果您小心地确保并发<code>更新</code>总是以相同的顺序获得相同的行,您仍然可以依赖答案第一部分中描述的行为。

令人沮丧的是,PostgreSQL不提供< code >更新...ORDER BY所以确保您的更新总是以相同的顺序选择相同的行并不像您希望的那样简单。<代码>选择...用于更新...ORDER BY后跟一个单独的< code>UPDATE通常是最安全的。

如果您正在执行具有多个阶段、涉及多个元组或相等以外的条件的查询,您可能会得到与串行执行结果不同的令人惊讶的结果。特别是,并发运行以下内容:

UPDATE test SET col = 1 WHERE col = (SELECT t.col FROM test t ORDER BY t.col LIMIT 1);

或者其他构建简单“队列”系统的努力将*失败*以达到您的预期。有关详细信息,请参阅有关并发的 PostgreSQL 文档和本演示文稿。

如果您希望工作队列由数据库支持,那么有经过充分测试的解决方案可以处理所有令人惊讶的复杂角落案例。其中最受欢迎的是PgQ。有一篇关于这个主题的有用的PgCon论文,谷歌搜索“postgresql queue”充满了有用的结果。

< sup>*顺便说一下,您可以使用< code>SELECT 1 FROM test WHERE col = 2进行更新,而不是< code > LOCK TABLE ;只在元组上获得写锁。这将阻止对它的更新,但不会阻止对其他元组的写入或任何读取。这允许您模拟不同种类的并发问题。

 类似资料:
  • 我正在将错误消息添加到登录屏幕。 虽然代码运行良好,并且执行我希望它执行的操作。它会在执行时导致约束错误。 以下是受影响的约束: 下面是导致errorView出现的函数。 如何在不破坏约束的情况下更改约束? 我尝试self.view.update约束()-但是什么也没有做。我还尝试在添加约束之前删除它们,但是仍然有一个错误。 任何帮助都将不胜感激! 编辑: 我找到了一个Objective-c解决方

  • 我需要使用两个线程将两个不同的字符附加到同一个JavaFX TextArea。我可以让一个工作,但是当我添加第二个线程时,它会因为一些很长的异常而中断。我做错了什么? 我研究了这个问题以寻求指导,它让我得到了一个线程工作,但不是两个:在两个独立的JavaFx TextArea中显示两个不同线程的输出

  • 我已经发现了许多其他类似的问题,但我不知道为什么它会在我的实体上这样做。用简单的英语表示如下。三个实体:账户、现场和合同。一个帐户可以有多个站点。一个帐户可以有多个合同。一个站点可以有多个合同。因此,在某些情况下,您可以将合同附加到站点,然后将其附加到帐户,或者将合同直接附加到帐户。 我假设我得到了这一点,因为删除合同有两种可能的级联,一种是在删除帐户时级联,另一种是直接附加到帐户,另一种是在删除

  • 问题内容: 尝试向表中添加约束时出现问题。我得到了错误: 在表“ Employee”上引入FOREIGN KEY约束“ FK74988DB24B3C886”可能会导致循环或多个级联路径。指定ON DELETE NO ACTION或ON UPDATE NO ACTION,或修改其他FOREIGN KEY约束。 我的约束是在一个表和一个表之间。该表包括,,,和。的具有多个字段的标号,从而可以存在对于每

  • 问题内容: 快速的问题,我已经尝试自己弄清楚这一点,但是在试图弄清页面为什么或如何重新加载以及正在/不应该执行其应做的工作时,使用会话变量可能会造成混淆。 在任何(非脚本)情况下,页面重新加载(使用JavaScript,f5,ctrl + f5,浏览器重新加载按钮等)是否会导致表单重新发布? (这与在C#代码中使用IfPost分支有关,例如下面的示例代码): 我只是需要知道在这里期望什么,以便可以

  • 我收到了这个错误 在表“区域”上引入FOREIGN KEY约束“FK_dbo.Regions_dbo.Countries_CountryId”可能会导致循环或多个级联路径。指定ON DELETE NO ACTION或ON UPDATE NO ACTION,或修改其他FOREIGN KEY约束。无法创建约束。请参阅以前的错误。 我想知道这是否意味着我的数据库设计不好?我听说你关掉了级联之类的东西,但