【问题标题】:apparent transaction isolation violation in postgresqlpostgresql中明显的事务隔离违规
【发布时间】:2018-02-05 23:03:35
【问题描述】:

我在 CentOS Linux 上使用 PostgreSQL 9.3.12。

我有两个进程连接到同一个数据库,使用“已提交”的默认事务隔离级别。根据 postgres 文档,事务中的一个进程在提交之前不应“看到”事务中另一个进程所做的更改。

我看到的一个序列是:

  • 进程 A 开始其事务
  • 进程 A 删除表 T 中的所有内容
  • 进程 B 开始其事务
  • 进程 B 尝试选择更新表 T 中的一行
  • 进程 B 出现空(0 行)并调用回滚
  • 进程 A 从传入数据重新填充表 T
  • 进程 A 提交其事务

现在,应该在两个事务开始之前填充表 T,并且进程 B 的查询应该出现一行。如果这些进程没有同时运行,它也会这样做。

我的理解是进程 B 应该看到表 T 中所需行的旧副本,进行更改,并且这些更改应该被进程 A 删除和重新填充表 T 破坏。我不知道为什么进程B 空了。

除了我自己对这些先决条件的完全误解之外,谁能想到我会看到这种行为的另一个原因?

不要担心糟糕的架构,它正在消失。我只是想了解为什么这种情况似乎违反了我理解的“已提交”事务隔离。

谢谢。

【问题讨论】:

  • 您是否可以使用“TRUNCATE TABLE”删除所有内容。我认为这不是事务性的。根据手册:“TRUNCATE 不是 MVCC 安全的。”
  • 不,这是一个 DELETE FROM。
  • 您使用哪些工具,您确定您的流程按预期交错运行吗?您是否使用“开始交易”?在我看来,B 真的不应该在 A 提交之前看到删除,而我在 postgres 方面的经验也从未如此。
  • 听起来好像进程 A 实际上处于自动提交模式
  • 您看到的可能与 MVCC 相关——“每个 SQL 语句都会看到一段时间前的数据快照(数据库版本),而不管底层数据的当前状态如何。” B 选择更新的行在 A 提交后不存在。相反,有一个 新行 恰好有一些(或全部)相同的值。

标签: postgresql transactions


【解决方案1】:

根据 postgres 文档,事务中的一个进程应该 直到他们在事务中“看到”另一个进程所做的更改 已提交。


是和否 - 像往常一样,这取决于。 The documentation 严格地说:

Read Committed 是 PostgreSQL 中的默认隔离级别。

当事务使用此隔离级别时,SELECT 查询(没有 FOR UPDATE/SHARE 子句)只看到查询前提交的数据 开始;它永远不会看到未提交的数据或已提交的更改 在并发事务的查询执行期间。实际上,一个 SELECT 查询在查询时看到数据库的快照 开始运行。但是,SELECT 确实看到了之前的效果 更新在它自己的事务中执行,即使它们不是 还承诺。另请注意,两个连续的 SELECT 命令可以看到 不同的数据,即使它们在单个事务中,如果 其他事务在第一个 SELECT 启动后提交更改,并且 在第二个 SELECT 开始之前。

UPDATE、DELETE、SELECT FOR UPDATE 和 SELECT FOR SHARE 命令 在搜索目标行方面的行为与 SELECT 相同:它们 只会找到在命令开始时提交的目标行 时间。但是,这样的目标行可能已经更新(或 被另一个并发事务删除或锁定) 成立。 在这种情况下,可能的更新程序将等待第一个 更新事务以提交或回滚(如果它仍在 进步)。如果第一个更新程序回滚,那么它的效果是 否定,第二个更新程序可以继续更新 最初发现行。如果第一个更新者提交,第二个更新者 如果第一个更新者删除了该行,将忽略该行,否则它将 尝试将其操作应用于行的更新版本。这 命令的搜索条件(WHERE 子句)被重新评估为 查看该行的更新版本是否仍然与搜索匹配 健康)状况。如果是这样,第二个更新程序继续使用它的操作 行的更新版本。在 SELECT FOR UPDATE 和 SELECT FOR SHARE,这意味着它是该行的更新版本 被锁定并返回给客户端。

换句话说,只是 SELECT 与 SELECT FOR UPDATE/DELETE/UPDATE 不同。

您可以创建简单的测试用例来观察该行为:

会话 1

test=> START TRANSACTION;
START TRANSACTION
test=> SELECT * FROM test;
 x
----
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
(10 rows)


test=> DELETE FROM test;
DELETE 10
test=>

现在登录另一个会话 2:

test=> START TRANSACTION;
START TRANSACTION
test=> SELECT * FROM test;
 x
----
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
(10 rows)


test=> SELECT * FROM test WHERE x = 5 FOR UPDATE;

在最后一个命令 SELECT ... FOR UPDATE 会话 1 “挂起”并且正在等待某些东西之后......


返回会话 1

test=> insert into test select * from generate_series(1,10);
INSERT 0 10
test=> commit;
COMMIT

现在,当您返回会话 2 时,您将看到:

test=> SELECT * FROM test WHERE x = 5 FOR UPDATE;
 x
---
(0 rows)


test=> select * from test;
 x
----
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
(10 rows)

也就是说 - 简单的 SELECT 仍然没有看到任何更改,而 SELECT ... FOR UPDATE 确实看到行已被删除。但它没有看到会话 1 插入的新行

实际上你看到的序列是:

  • 进程 A 开始其事务
  • 进程 A 删除表 T 中的所有内容
  • 进程 B 开始其事务
  • 进程 B 尝试选择更新表 T 中的一行
  • 进程 B “挂起”并等待会话 A 提交或回滚
  • 进程 A 从传入数据重新填充表 T
  • 进程 A 提交其事务
  • 进程 B 出现空(0 行 - 在会话 A 提交之后)并调用回滚

【讨论】:

  • 重复您的测试,但将您的选择语句替换为select *, xmin, xmax from test;。元组头包含xminxmax;大多数元数据中没有报告它们,但您可以通过在 select... 语句中包含它们的名称来查看它们。 “B”看到 xminxmax 的不同值。
猜你喜欢
  • 2014-03-07
  • 1970-01-01
  • 1970-01-01
  • 2018-10-23
  • 2016-04-26
  • 2017-07-08
  • 2015-03-26
  • 2021-02-08
  • 1970-01-01
相关资源
最近更新 更多