在 postgres 中,如果我运行以下语句
update table set col = 1 where col = 2
在默认READ COMMITTED
隔离级别下,来自多个并发会话,我保证:
- 在单个匹配的情况下,只有 1 个线程将获得 1 的 ROWCOUNT(意味着只有一个线程写入)
- 在多重匹配的情况下,只有 1 个线程将获得 ROWCOUNT > 0(意味着只有一个线程写入批处理)
在 postgres 中,如果我运行以下语句
update table set col = 1 where col = 2
在默认READ COMMITTED
隔离级别下,来自多个并发会话,我保证:
您声明的保证适用于这种简单的情况,但不一定适用于稍微复杂的查询。有关示例,请参见答案的末尾。
假设 col1 是唯一的,只有一个值“2”,或者具有稳定的顺序,因此每个都UPDATE
以相同的顺序匹配相同的行:
这个查询会发生什么,线程将找到 col=2 的行,并且都尝试在该元组上获取写锁。其中有一个会成功。其他人将阻塞等待第一个线程的事务提交。
第一个 tx 将写入、提交并返回行数 1。提交将释放锁。
其他 tx 将再次尝试获取锁。他们会一个接一个地成功。每笔交易将依次经历以下过程:
WHERE col=2
获得锁后重新检查条件。UPDATE
将跳过该行。UPDATE
因此它将报告零行更新。在这个简单的情况下,行级锁定和条件重新检查有效地序列化更新。在更复杂的情况下,不是那么多。
你可以很容易地证明这一点。打开说四个 psql 会话。BEGIN; LOCK TABLE test;
首先,用*锁定表。在其余会话中运行相同UPDATE
的 s - 它们将阻塞表级锁。COMMIT
现在通过ting 你的第一个会话来释放锁。看他们比赛。只有一个会报告行数为 1,其他人会报告 0。这很容易自动化并编写脚本以重复并扩展到更多连接/线程。
要了解更多信息,请阅读PostgreSQL 并发问题的第 11 页并发写入规则- 然后阅读该演示文稿的其余部分。
正如凯文在评论中指出的那样,如果col
不是唯一的,因此您可能会匹配多行,那么不同的执行UPDATE
可能会得到不同的排序。如果他们选择不同的计划(比如一个是通过 a PREPARE
,EXECUTE
另一个是直接的,或者你在搞乱enable_
GUC)或者他们都使用的计划使用不稳定的等值,就会发生这种情况。如果他们以不同的顺序获取行,则 tx1 将锁定一个元组,tx2 将锁定另一个元组,然后他们将各自尝试锁定彼此已经锁定的元组。PostgreSQL 会以死锁异常中止其中一个。这也是为什么您的所有数据库代码都应该始终准备好重试事务的另一个很好的理由。
如果您小心确保 concurrentUPDATE
始终以相同的顺序获取相同的行,您仍然可以依赖答案第一部分中描述的行为。
令人沮丧的是,PostgreSQL 没有提供UPDATE ... ORDER BY
,因此确保您的更新始终以相同的顺序选择相同的行并不像您希望的那么简单。ASELECT ... FOR UPDATE ... ORDER BY
后跟一个单独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”充满了有用的结果。
* 顺便说一句,LOCK TABLE
您可以使用而不是 aSELECT 1 FROM test WHERE col = 2 FOR UPDATE;
来获取元组上的写锁。这将阻止针对它的更新,但不会阻止对其他元组的写入或阻止任何读取。这使您可以模拟不同类型的并发问题。