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

Vla*_*cea 7 oracle postgresql transactions acid serializable

我注意到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;  
Run Code Online (Sandbox Code Playgroud)

有以下数据:

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); 
Run Code Online (Sandbox Code Playgroud)

如果我打开两个单独的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
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

UPDATE

我甚至更改了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;
Run Code Online (Sandbox Code Playgroud)

然后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.
Run Code Online (Sandbox Code Playgroud)

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

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

顺便说一下,你可以自己运行所有这些例子,因为它们在GitHub上.

Vla*_*cea 5

在 1995 年的论文A Critique of ANSI SQL Isolation Levels 中,Jim Gray 和他的同事将 Phantom Read 描述为:

P3: r1[P]...w2[y in P]...(c1 or a1) (Phantom)

一个重要的注意事项是,ANSI SQL P3 仅禁止对谓词进行插入(和更新,根据某些解释),而上述 P3 的定义禁止在谓词被读取后进行任何满足谓词的写操作——写操作可以是插入、更新,或删除。

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

在最初的 SQL Server 2PL(两阶段锁定)实现中,对于隐含谓词锁的查询返回相同的结果。

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

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

  1. 发送1: SELECT SUM(salary) FROM employee where company_id = 1;
  2. 发送2: INSERT INTO employee (id, name, company_id, salary) VALUES (100, 'John Doe', 1, 100000);
  3. 发送1: UPDATE employee SET salary = salary * 1.1;
  4. 发送2: COMMIT;
  5. 发送1: COMMIT:

在这种情况下,CEO 运行第一笔交易 (Tx1),因此:

  1. 她首先检查公司所有工资的总和。
  2. 同时,人力资源部门运行第二笔交易(Tx2),因为他们刚刚设法雇用了 John Doe 并给了他 10 万美元的薪水。
  3. 考虑到工资总额,CEO 决定加薪 10% 是可行的,不知道工资总额已经增加了 10 万。
  4. 同时,提交了 HR 事务 Tx2。
  5. Tx1 已提交。

繁荣!CEO 已经对旧快照做出了决定,提出了当前更新的工资预算可能无法维持的加薪。

您可以在以下帖子中查看此用例的详细说明(带有大量图表)。

这是幻读还是写歪斜

根据Jim Gray 和 co 的说法,这是一个幻读,因为写偏斜被定义为:

A5B Write Skew 假设T1读取x和y,与C()一致,然后一个T2读取x和y,写入x,并提交。然后T1 写y。如果 x 和 y 之间存在约束,则可能会违反该约束。在历史上:

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

在 Oracle 中,事务管理器可能会或可能不会检测到上述异常,因为它不像 MySQL那样使用谓词锁或索引范围锁(下一个键锁)

只有当 Bob 对员工表发出读操作时,PostgreSQL 才能设法捕捉到这种异常,否则无法阻止这种现象。

更新

最初,我假设 Serializability 也意味着时间排序。然而,正如Peter Bailis 很好地解释的那样,挂钟排序或线性化仅适用于严格的可串行化。

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

因此,根据 Serializable 定义,如果第二个事务不发出任何读取,则可能会发生这种 Phantom Read。但是,在 2PL 提供的 Strict Serializable 模型中,即使第二个事务没有针对我们试图防止幻读的相同条目发出读取,幻读也会被阻止。