postgresql

时间:2017-08-28 15:58:55

标签: postgresql transactions

我在CentOS Linux上使用PostgreSQL 9.3.12。

我有两个进程连接到同一个数据库,使用默认事务隔离级别“read committed”。根据postgres文档,事务中的一个进程不应“看到”事务中另一个进程所做的更改,直到它们被提交为止。

我看到的一个序列是:

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

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

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

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

不要担心糟糕的建筑,它会消失。我只是想了解为什么这种情况似乎违反了我所理解的“读取已提交”事务隔离。

感谢。

1 个答案:

答案 0 :(得分: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相同:它们   将只查找从命令start开始提交的目标行   时间。但是,这样的目标行可能已经更新(或   当它被另一个并发事务删除或锁定时   找到。 在这种情况下,可能的更新程序将等待第一个   更新事务以提交或回滚(如果它仍在   进展)。如果第一个更新程序回滚,那么它的效果是   否定,第二个更新程序可以继续更新   最初找到了一排。如果第一个更新程序提交,则第二个更新程序   如果第一个更新程序删除了,将忽略该行,否则它将   尝试将其操作应用于行的更新版本。该   命令的搜索条件(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&#34之后;挂起"并在等待......

返回会话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提交后)并调用回滚