Oracle中的Phantom Read异常和PostgreSQL不会回滚事务

时间:2016-09-19 07:09:01

标签: oracle postgresql transactions serializable acid

我注意到Oracle和PostgreSQL中都出现了以下情况。

考虑到我们有以下数据库架构:

create table post (
    id int8 not null, 
    title varchar(255), 
    version int4 not null, 
    primary key (id));    

create table post_comment (
    id int8 not null, 
    review varchar(255), 
    version int4 not null, 
    post_id int8, 
    primary key (id));

alter table post_comment 
    add constraint FKna4y825fdc5hw8aow65ijexm0 
    foreign key (post_id) references post;  

使用以下数据:

insert into post (title, version, id) values ('Transactions', 0, 1);
insert into post_comment (post_id, review, version, id) 
    values (1, 'Post comment 1', 459, 0);
insert into post_comment (post_id, review, version, id) 
    values (1, 'Post comment 2', 537, 1);
insert into post_comment (post_id, review, version, id) 
    values (1, 'Post comment 3', 689, 2); 

如果我打开两个单独的SQL控制台并执行以下语句:

TX1: BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;

TX2: BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;

TX1: SELECT COUNT(*) FROM post_comment where post_id = 1;

TX1: > 3

TX1: UPDATE post_comment SET version = 100 WHERE post_id = 1;

TX2: INSERT INTO post_comment (post_id, review, version, id) VALUES (1, 'Phantom', 0, 1000);

TX2: COMMIT;

TX1: SELECT COUNT(*) FROM post_comment where post_id = 1;

TX1: > 3

TX1: COMMIT;

TX3: SELECT * from post_comment;

     > 0;"Post comment 0";100;1
       1;"Post comment 1";100;1
       2;"Post comment 2";100;1
       1000;"Phantom";0;1

正如预期的那样,SERIALIZABLE隔离级别保留了TX1事务开始时的快照数据,TX1只能看到3个post_comment个记录。

由于Oracle和PostgreSQL中的MVCC模型,TX2允许插入新记录并提交。

为什么允许TX1提交?因为这是一个幻像读取异常,我期待看到TX1将回滚“序列化失败异常”或类似的东西。

PostgreSQL和Oracle中的MVCC Serializable模型是否仅提供快照隔离保证但没有幻像读取异常检测?

更新

我甚至更改了Tx1以发出UPDATE语句,该语句更改属于同一version的所有post_comment条记录的post列。

这样,Tx2会创建一条新记录,而Tx1将在不知道已添加满足UPDATE过滤条件的新记录的情况下提交。

实际上,在PostgreSQL上使其失败的唯一方法是在插入幻像记录之前在Tx2中执行以下COUNT查询:

Tx2: SELECT COUNT(*) FROM post_comment where post_id = 1 and version = 0

TX2: INSERT INTO post_comment (post_id, review, version, id) VALUES (1, 'Phantom', 0, 1000);

TX2: COMMIT;

然后Tx1将回滚:

org.postgresql.util.PSQLException: ERROR: could not serialize access due to read/write dependencies among transactions
  Detail: Reason code: Canceled on identification as a pivot, during conflict out checking.
  Hint: The transaction might succeed if retried.

写入偏差异常预防机制很可能检测到此更改并回滚了事务。

有趣的是,Oracle似乎并没有被这种异常所困扰,因此Tx1只是成功提交。由于Oracle不会阻止写入偏差的发生,因此Tx1提交juts很好。

顺便说一句,您可以自己运行所有这些示例,因为它们位于GitHub

4 个答案:

答案 0 :(得分:3)

您观察到的是幻像阅读。那就是如果第二次发出查询时会出现一个新行(幻像出现意外)。

SERIALIZABLE隔离的情况下,Oracle和PostgreSQL中的幻像读取都受到保护。

Oracle和PostgreSQL之间的区别在于Oracle中的SERIALIZABLE隔离级别仅提供快照隔离(这足以防止幻像出现),而在PostgreSQL中它将保证真正的可串行化(即,始终存在导致相同结果的SQL语句的序列化)。如果你想在Oracle和PostgreSQL中使用相同的东西,请在PostgreSQL中使用REPEATABLE READ隔离。

答案 1 :(得分:3)

我喜欢这个问题,因为它表明SQL Standard中的Phantom Read定义只能描述效果,而没有说明此数据异常的根本原因:

  

P3(“Phantom”):SQL事务T1读取行N的集合                   满足一些。然后是SQL事务T2                   执行生成一行或多行的SQL语句                   满足SQL事务T1使用的。如果                   然后,SQL事务T1重复使用相同的初始读取                   ,它获得了不同的行集合。

在1995年的论文中,A Critique of ANSI SQL Isolation Levels,吉姆格雷和合伙人将幻影阅读描述为:

  

P3:r1 [P] ... w2 [在P中的y] ......(c1或a1)(幻影)

     

一个重要的注意事项是ANSI SQL P3仅禁止插入(和   根据一些解释更新到谓词而不是   上面P3的定义禁止任何满足谓词的写入   一旦读取了谓词 - 写入可以是插入,   更新或删除。

因此,Phantom Read并不意味着您可以简单地返回当前正在运行的事务开始时的快照,并假装为查询提供相同的结果将保护您免受实际的Phantom Read异常。< / p>

在原始的SQL Server 2PL(两阶段锁定)实现中,为查询返回相同的结果意味着Predicate Locks。

MVCC(多版本并发控制)快照隔离(在Oracle中错误地命名为Serializable)实际上并不阻止其他事务插入/删除与相同过滤条件匹配的行与已执行并返回结果集的查询我们当前正在运行的交易。

出于这个原因,我们可以想象以下场景,我们希望将加薪应用于所有员工:

  1. Tx1:SELECT SUM(salary) FROM employee where company_id = 1;
  2. Tx2:INSERT INTO employee (id, name, company_id, salary) VALUES (100, 'John Doe', 1, 100000);
  3. Tx1:UPDATE employee SET salary = salary * 1.1;
  4. Tx2:COMMIT;
  5. Tx1:COMMIT:
  6. 在这种情况下,CEO会运行第一笔交易(Tx1),所以:

    1. 她首先检查公司所有工资的总和。
    2. 与此同时,人力资源部负责管理第二笔交易(Tx2),因为他们刚刚设法聘请John Doe并给了他10万美元的薪水。
    3. 首席执行官决定,考虑到工资总额,10%的加薪是可行的,不知道工资总额上升了10万。
    4. 同时,人力资源交易Tx2已提交。
    5. 首席执行官交易Tx1已下定。
    6. 轰!首席执行官已就旧的快照作出决定,并提出可能无法通过当前更新的薪资预算维持的加薪。

      您可以在the following post中查看此用例的详细说明(包含大量图表)。

      这是Phantom Read还是Write Skew

      根据Jim Gray and co,这是一个Phantom Read,因为Write Skew被定义为:

        

      A5B写入偏斜假设T1读取x和y,这与x一致   C(),然后T2读取x和y,写入x和提交。然后是T1   写道。如果x和y之间存在约束,则可能是   侵犯。就历史而言:

           

      A5B:r1 [x] ... r2 [y] ... w1 [y] ... w2 [x] ...(c1和c2发生)

      在Oracle中,事务管理器可能会或可能不会检测到上面的异常,因为它不使用谓词锁或index range locks (next-key locks),如MySQL。

      只有当Bob针对employee表发出读取时,PostgreSQL才会设法捕获此异常,否则,这种现象不会被阻止。

      更新

      最初,我假设Serializability也意味着时间排序。但是,作为very well explained by Peter Bailis,只有严格可序列化才会假设挂钟排序或线性化。

      因此,我的假设是针对Strict Serializable系统进行的。但这不是Serializable应该提供的。 Serializable隔离模型不保证时间,只要它们等同于某些串行执行,就允许重新排序操作。

      因此,根据Serializable定义,如果第二个事务没有发出任何读取,就会发生这样的幻像读取。但是,在2PL提供的严格可序列化模型中,即使第二个事务没有针对我们试图防止幻像读取的相同条目发出读取,也会阻止幻像读取。

答案 2 :(得分:0)

Postgres文档defines a phantom read as:

  

事务重新执行返回一组满足的行的查询   一个搜索条件,并发现满足该行的行集   由于另一个最近提交的交易,情况发生了变化。

因为您的select在提交的其他事务之前和之后返回相同的值,所以它不符合幻像读取的条件。

答案 3 :(得分:0)

我只想指出Vlad Mihalcea的答案是完全错误的。

  

这是Phantom Read还是Write Skew?

这些都没有 - 这里没有异常,交易可序列化为Tx1 - &gt; TX2。

SQL标准规定: &#34;可序列化执行被定义为执行同时执行SQL事务的操作,这些操作产生与那些相同SQL事务的某些串行执行相同的效果。&#34; < / p>

  只有当Bob针对employee表发出读取时,PostgreSQL才设法捕获此异常,否则不会阻止此现象。

PostgreSQL在这里的行为是100%正确的,只是&#34;翻转&#34;明显的交易顺序。