在上小节中,事务的一致性反映的是某一个事务在其它并发事务“眼中”的状态。本小节要介绍事务的隔离性,是某一个事务执行过程中,它“眼中”其它所有并发事务的状态。一致性和隔离性,两者相互联系,在openGauss中均是基于MVCC和快照实现的;同时,两者又有一定区别,对于较高的隔离级别,除了MVCC和快照之外,还需要辅以其它的机制来实现。
如表10-2所示,在数据库业界,一般将隔离性按由低到高分为以下四个隔离级别,每个隔离级别按照在该级别下禁止发生的异常现象来定义。这些异常现象包括:
脏读,指一个事务在执行过程中读到并发的、还没有提交的写事务的修改内容。
不可重复读,指在同一个事务内,先后两次读到的同一条记录的内容发生了变化(被并发的写事务修改)。
幻读,指在同一个事务内,先后两次执行的、谓词条件相同的范围查询,返回的结果不同(并发写事务插入了新的记录)。
隔离级别越高,在一个事务执行过程中,它能“感知”到的并发事务的影响越小。在最高的可串行化隔离级别下,任意一个事务的执行,均“感知”不到有任何其它并发事务执行的影响,并且所有事务执行的效果就和一个挨一个顺序执行的效果完全相同。
表10-2 事务隔离级别
| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
| 读未提交 | 允许 | 允许 | 允许 |
| 读已提交 | 不允许 | 允许 | 允许 |
| 可重复读 | 不允许 | 不允许 | 允许 |
| 可串行化 | 不允许 | 不允许 | 不允许 |
在openGauss中,隔离级别的实现基于MVCC和快照机制,因此这种隔离方式被称为快照隔离(Snapshot Isolation,简称SI)。目前,openGauss支持读已提交(Read Committed)和可重复读(Repeatable Read)这两种隔离级别。两者实现上的差别在于在一个事务中获取快照的次数。
如果采用读已提交的隔离级别,那么在一个事务块中每条语句的执行开始阶段,都会去获取一次最新的快照,从而可以看到那些在本事务块开始以后、在前面语句执行过程中提交的并发事务的效果。如果采用可重复读的隔离级别,那么在一个事务块中,只会在第一条语句的执行开始阶段,获取一次快照,后面执行的所有语句都会采用这个快照,整个事务块中的所有语句均不会看到该快照之后提交的并发事务的效果。
我们通过具体的例子来说明一下读已提交和可重复读的区别。
考虑以下三个并发执行的事务(表t包含一个整型字段a):
T1:
START TRANSACTION;
INSERT INTO t VALUES (v1);
COMMIT;
T2:
START TRANSACTION;
INSERT INTO t VALUES (v2);
COMMIT;
T3:
START TRANSACTION;
SELECT * FROM t;
SELECT * FROM t;
SELECT * FROM t;
COMMIT;
这三个事务的并发执行顺序如图10-10所示。我们考虑T3事务三条查询的返回结果。如果采用读已提交的隔离级别,那么在第一条查询开始时,首次获取快照,T1和T2均没有提交,因此它们都在快照中,查询结果不会包含它们插入的新记录;在第二条查询开始时,第二次获取快照,T1已经提交,在第二条查询语句的快照中,只有T2,因此可以查询到T 1插入的记录v1;同理,在第三条查询开始时,第三次获取快照,T1和T2均已经提交,它们都不在第三条语句的快照中,因此可以查询到它们插入的记录v1和v2。
另一方面,如果采用可重复读的隔离级别,对于T3中的三条查询语句,均会采用第一条语句执行开始时的快照,而T1和T2均在该快照中,因此在该隔离级别下,T3的三条查询语句均不会返回v1和v2。

图10-10 读已提交和可重复读隔离级别在并发事务下的表现区别




