【问题标题】:Postgres read commited doesn't re read updated rowPostgres 读取已提交不会重新读取更新的行
【发布时间】:2020-08-30 01:22:22
【问题描述】:

美好的一天。我在 postgres 中使用READ COMMITTED 隔离级别,发现不符合官方文档的奇怪行为。 假设我有一张表account(id int,name text,amount int) 和两行。

test> select * from account;                       
-[ RECORD 1 ]-------------------------
id     | 1
name   | Bob
amount | 800
-[ RECORD 2 ]-------------------------
id     | 2
name   | Bob
amount | 200

现在我开始两个 READ COMMITTED 事务。第一个执行以下查询

UPDATE account set amount = 100 where id = 2; -- 1

然后第二个执行这个查询

 UPDATE account set amount = amount+50 --2
  where name in 
      (select DISTINCT name from account group by
      name having sum(amount)>=1000); 

现在它被锁定了,因为第一个事务还没有提交。所以第二笔交易要给总金额大于或等于 1000 的每个账户加 50。由于 Bob 有两个账户(800+200),那么它应该给每个账户加 50。但是,现在第一个事务已提交COMMIT; --1,现在 Bob 总共有 900 个,根据Documentation,读取已提交事务将

命令的搜索条件(WHERE 子句)是 重新评估以查看该行的更新版本是否仍然匹配 搜索条件。如果是这样,第二个更新程序继续其 使用行的更新版本进行操作

据我了解,第二笔交易将重新评估 where 条件并跳过 Bob 的帐户。然而,当第二个事务被提交时,最后的行看起来像这样

id     | 1                                                                                            │
name   | Bob                                                                                          │
amount | 850                                                                                          │
-[ RECORD 3 ]-------------------------                                                                │
id     | 2                                                                                            │
name   | Bob                                                                                          │
amount | 150 

这意味着第二个事务没有重新评估搜索条件并对行应用更新,即使它们不符合条件。为什么会这样。我错过了文档中的某些内容吗?

【问题讨论】:

  • HAVING 子句不是 WHERE 子句。
  • @pifor 我谈到了第二笔交易调用时的一部分(其中名称在(...)中)
  • WHERE 子句是可能应用以下规则的子查询: 当事务使用此隔离级别时,SELECT 查询(没有 FOR UPDATE/SHARE 子句)只能看到之前提交的数据查询开始;它永远不会看到未提交的数据或并发事务在查询执行期间提交的更改

标签: postgresql isolation read-committed


【解决方案1】:

第一个事务中的UPDATE 阻塞了第二个查询中的UPDATE,但不是该查询中的子选择。子选择已经完成,总和已确定为 1000,因此执行 UPDATE 并阻止 that。当锁消失时,子查询重新计算。

您从文档中引用的段落是关于您不使用的SELECT ... FOR UPDATE(或FOR SHARE)。它不能在您的示例中使用,因为它在使用聚合函数或分组的查询中没有意义。

【讨论】:

  • 感谢您的回复。我了解子选择不会重新评估。但是,关于你的第二段。我发布的报价不仅仅是关于更新/共享。请参阅,postgres 站点中的段落以“UPDATE、DELETE、SELECT FOR UPDATE 和 SELECT FOR SHARE”开头,然后才说“在 SELECT FOR UPDATE 和 SELECT FOR SHARE 的情况下,这意味着它是更新的...... " 所以重新评估也适用于 UPDATE,不是吗?
  • 被阻塞的UPDATE在锁消失后重新读取行,是的。这就是为什么你最终会得到 150 的值。但在 UPDATE account SET ... WHERE amount = 100 中,WHERE 子句将不会重新评估。这与SELECT ... FOR UPDATE 不同。
  • @LaurenzAlbe,您说“您从文档中引用的段落是关于 SELECT ... FOR UPDATE”,但事实并非如此。这篇文章实际上是关于所有 update\delete\select_for_update_or_share 案例:UPDATE、DELETE、SELECT FOR UPDATE 和 SELECT FOR SHARE 命令... ..重新评估命令的条件(WHERE 子句)。这与 9.5 和 13 版本有关。它仍然看起来像 postgres 中的错误或(更有可能)文档中的错误。请你再读一遍那段并说出你的想法吗?
  • 我创建了一个问题并从 pgsql-docs 得到了明确的答案。他们说:“也许细微差别消失了,文档可以改进,但它清楚地说“......看看行的更新版本是否仍然匹配搜索条件” - 即,它会愉快地跳过它认为的行, 在它开始等待之前, 它必须更新但它不会去寻找现在可能匹配标准的新行. 它也不会以同样的理由处理任何插入. 前导句加强了这一点: "...他们只会找到在命令开始时间提交的目标行。""
  • @alexeyivanov 是的,这篇文章也是关于UPDATE。但有问题的情况是没有FOR UPDATE 的(子)选择。 SELECT 不会阻塞,因此不会重新评估任何内容。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-05-12
  • 1970-01-01
  • 2014-02-16
相关资源
最近更新 更多