update table set col = 1 where col = 2
在默认的READ COMMITTED
isolation level中,从多个并发会话中,我保证:
>在单个匹配的情况下,只有1个线程将获得ROWCOUNT为1(意味着只有一个线程写入)
>在多匹配的情况下,只有1个线程将获得ROWCOUNT> 0(表示只有一个线程写入批处理)
这个简单的案例
假设col1是唯一的,只有一个值“2”,或者具有稳定的排序,因此每个UPDATE都以相同的顺序匹配相同的行:
这个查询会发生什么,线程会找到col = 2的行,并且所有人都尝试在该元组上获取写锁.其中只有一个会成功.其他人将阻止等待第一个线程的事务提交.
第一个tx将写入,提交并返回1的rowcount.提交将释放锁.
其他tx将再次尝试抢锁.他们一个接一个地成功.每笔交易将依次经过以下过程:
>获取有争议的元组的写锁定.
>获取锁定后重新检查WHERE col = 2条件.
>重新检查将显示条件不再匹配,因此UPDATE将跳过该行.
> UPDATE没有其他行,因此它将报告零行已更新.
>提交,释放下一个tx的锁,试图抓住它.
在这种简单的情况下,行级锁定和条件重新检查有效地序列化更新.在更复杂的情况下,没有那么多.
你可以轻松地证明这一点.打开说四个psql会话.在第一个中,用BEGIN锁定表格; LOCK TABLE测试; *.在其余的会话中运行相同的UPDATE – 它们将阻止表级锁定.现在通过COMMITting您的第一个会话释放锁定.看他们比赛.只有一个会报告行数为1,其他人将报告0.这很容易自动化并编写脚本以便重复和扩展到更多连接/线程.
要了解更多信息,请阅读rules for concurrent writing,第11页,共rules for concurrent writing页 – 然后阅读该演示文稿的其余部分.
如果col1不是唯一的?
正如Kevin在评论中指出的那样,如果col不是唯一的,那么你可能匹配多行,那么UPDATE的不同执行可能会得到不同的顺序.如果他们选择不同的计划(例如,一个是通过PREPARE和EXECUTE而另一个是直接的,或者你正在搞乱enable_ GUC)或者他们所有使用的计划都使用不稳定的相等值,那么就会发生这种情况.如果他们以不同的顺序获取行,那么tx1将锁定一个元组,tx2将锁定另一个元组,然后他们每个都会尝试锁定彼此已经锁定的元组. Postgresql将使用死锁异常中止其中一个.这是为什么所有数据库代码应始终准备重试事务的另一个好理由.
如果您小心确保并发UPDATE总是以相同的顺序获得相同的行,您仍然可以依赖于答案第一部分中描述的行为.
令人沮丧的是,Postgresql不提供UPDATE … ORDER BY,因此确保您的更新始终以相同的顺序选择相同的行并不像您希望的那样简单. SELECT … 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 docs on concurrency和this presentation.
如果您想要一个由数据库支持的工作队列,那么有经过充分测试的解决方案可以处理所有令人惊讶的复杂角落案例.其中最受欢迎的是PgQ.关于这个话题有一个有用的PgCon paper,而a Google search for ‘postgresql queue’则充满了有用的结果.
* BTW,您可以使用SELECT 1 FROM test WHERE col = 2 FOR UPDATE;而不是LOCK TABLE.在元组上获取写锁定.这将阻止对它的更新,但不阻止写入其他元组或阻止任何读取.这允许您模拟不同类型的并发问题.