目录
openGauss 数据库 SQL 引擎
openGauss 数据库执行器技术
openGauss 存储技术
openGauss 事务机制
Ⅰ.openGauss 数据库事务概览
Ⅱ.openGauss 事务 ACID 个性介绍
1.openGauss 中的事务持久性
2.openGauss 中的事务原子性
3.openGauss 中的事务一致性
4.openGauss 中的事务隔离性
Ⅲ.openGauss 并发管制
Ⅳ.openGauss 分布式事务
openGauss 数据库安全
openGauss 事务机制
openGauss 事务 ACID 个性介绍
本节次要介绍 openGauss 中如何保障单机事务的 ACID,在此基础上,在之后文章的第四节中将阐明如何保障分布式事务的 ACID。
openGauss 中的事务持久性 01
和业界简直所有的数据库一样,openGauss 通过将事务对于数据库的批改写入能够永恒(长时间)保留的存储介质中,来保障事务的持久性。这个过程被称为事务的长久化过程。长久化过程是保障事务持久性所必不可少的环节,其效率对于数据库整体性能影响很大,经常成为数据库的性能瓶颈所在。
最罕用的存储介质是磁盘。对于磁盘来说,其每次读写操作都有一个“启动”代价,因而在单位工夫内(每秒内),一个磁盘能够进行的读写操作次数(Input/Output Operations Per Second,简称 IOPS)是有下限的。HDD 磁盘的 IOPS 个别在 1000 次 / 秒以下,SSD 磁盘的 IOPS 能够达到 10000 次 / 秒左右。另外一方面,如果多个磁盘读写申请的数据在磁盘上是相邻的,那么能够被合并为一次读写操作,这导致磁盘程序读写的性能通常要远优于随机读写。
一般来说,尤其是在 OLTP 场景下,用户对于数据库数据的批改是比拟扩散随机的。如果在长久化过程中,间接将这些扩散的数据写入磁盘,那么这个随机写入的性能是比拟差的。因而,数据库通常都采纳预写日志(Write Ahead Log,简称 WAL)来防止长久化过程中的随机 IO,如图 4(a) 所示。所谓预写日志,是指在事务提交的时候,先将事务对于数据库的批改写入一个程序追加的 WAL 日志文件中。因为 WAL 日志的写操作是程序 IO,因而其能够达到一个比拟高的性能。另一方面,对于真正批改的物理数据文件,再期待适合的机会写入磁盘,以尽可能合并该数据文件上的 IO 操作。
在一个事务实现日志的下盘操作(即写入磁盘)当前,该事务就能够实现提交动作。如果在此之后数据库产生宕机,那么数据库会首先从曾经写入磁盘的 WAL 文件中复原出该事务对于数据库的批改操作,从而保障事务一旦提交即具备持久性的特点。
上面联合图 4(b) 中的例子,简略阐明数据库故障复原的原理。假如一个事务须要在表 A(对应数据文件 A)和表 B(对应数据文件 B)中各插入一行新的记录,在数据库外部,其执行的程序如下:(1)记录批改数据文件 A 的日志,(2)记录批改数据文件 B 的日志,(3)在数据文件 A 中写入新的记录,(4)在数据文件 B 中写入新的记录。在上述过程中,如果在第(4)步执行时数据库产生宕机,那么该事务对于数据文件 B 的批改可能全副或局部失落。当数据库再次启动当前,在其可能承受新的业务之前,须要将这些可能失落的批改从日志中找回来(该操作被称为日志回放操作)。
在日志回放过程中,数据库会依据日志记录的先后顺序,顺次读取每个日志的内容,而后判断该日志记录的事务对数据库数据文件的批改是否和以后相干数据文件的内容统一。如果统一,阐明上次数据库停机之前批改曾经写入数据文件中,该日志批改无需回放;如果不统一,阐明上次数据库停机之前批改还未写入数据文件中,上次数据库停机可能是异样宕机导致,该日志对应的事务操作须要从新在相干数据文件中再次执行,能力保障复原胜利。
对于本例,在数据库复原过程中,首先读取到在数据文件 A 中插入记录的日志,将数据文件 A 读取上来之后,发现数据文件 A 中曾经蕴含该记录,因而该日志无需回放;而后读取到在数据文件 B 中插入记录的日志,将数据文件 B 读取上来之后,发现数据文件 B 中未蕴含新插入的记录,因而须要将日志中的记录再次写入到数据文件 B 中,从而实现复原。最终,该事务对于数据库所有的批改都得以复原进去,事务的持久性失去了保障。
(a) WAL 日志和数据页面的关系示意图
(b)WAL 日志和故障复原示意图
图 4 WAL 日志和事务持久性示意图
openGauss 中的事务原子性 02
如图 5 所示,openGauss 通过 WAL 日志、事务提交日志以及更新记录的多版本来保障写事务的原子性。
图 5 openGauss 事务的原子性示意图
(1)对于插入事务,例如以下插入事务:
START TRANSACTION;
INSERT INTO t(a) VALUES (v1);
INSERT INTO t(a) VALUES (v2);
COMMIT;
通常,咱们将一条记录在数据库外部的物理组织形式称为元组,其在模式上相似一个构造体。在上述插入事务的执行过程中,对于每一条新插入的记录,在它们元组构造体头部的 xmin 成员处都附加了插入事务的惟一标识,即一个全局递增的事务号(Transaction ID,简称 XID)。如 10.2.1 节中所述,这两条插入的记录(元组)连同它们的头部会被程序写入 WAL 日志中。
在该事务的提交阶段,在 WAL 日志中,会插入一条事务提交日志,以长久化该事务的提交后果,并会在专门的事务提交信息日志(Commit LOG,CLOG)中记录该事务号对应的事务提交后果(提交还是回滚)。尔后,如果有查问事务读到这两条记录,会首先去 CLOG 中查问记录头部事务号对应的提交信息,如果为提交,并且通过可见性判断,那么这两条记录会在查问后果中返回;如果 CLOG 中事务号为回滚状态,或者 CLOG 中事务号为提交状态然而该事务号对该查问不可见,那么这两条记录不会在查问后果中返回。如上,在没有故障产生的状况下,上述插入两行记录的事务是原子的,不会产生只看到插入一条的“中间状态”。
上面思考故障场景。
§ 如果在事务写下提交日志之前,数据库产生宕机,那么数据库复原过程中尽管会把这两条记录插入到数据页面中,然而并不会在 CLOG 中将该插入事务号标识为提交状态,后续查问也不会返回这两条记录。
§ 如果在事务写下提交日志之后,数据库产生宕机,那么数据库复原过程中,不仅会把这两条记录插入到数据页面中。同时,还会在 CLOG 中将该插入事务号标识为提交状态,后续查问能够同时看见这两条插入的记录。如上,在故障场景下,上述插入两行记录的事务操作亦是原子的。
(2)对于删除事务,例如:
START TRANSACTION;
DELETE FROM t WHERE a = v1;
DELETE FROM t WHERE a = v2;
COMMIT;
在该删除事务的执行过程中,对于下面每一条被删除的记录,在它们元组头部的 xmax 成员处都附加了删除事务的事务号。同时,与插入操作雷同,该删除事务的提交状态通过事务提交日志物化,并记录到 CLOG 中。从而,无论在失常场景还是故障场景下,如果后续查问波及上述被删除的那些记录,它们的可见性均取决于对立的、在 CLOG 中记录的删除事务的状态,不会产生局部记录能查问到、局部记录不能查问到的“中间状态”。
(3)对于更新事务,例如:
START TRANSACTION;
UPDATE t set a = v1’WHERE a = v1;
UPDATE t set a = v2’WHERE a = v2;
COMMIT;
在 openGauss 中,上述更新事务等同于先删除 v1 和 v2 这两行老版本记录,再插入 v1 和 v2 这两行新版本记录,删除和插入事务的原子性曾经在(1)和(2)中阐明,因而更新事务亦是原子的。
未完待续 ……