openGauss

开源数据库

openGauss社区官网

开源社区

openGauss事务管理系统分析

foreverdragon2021-12-04openGauss系统源码分析

Opengauss 事务管理系统分析

一、概述:

opengauss 的事务处理系统一共有三层:顶层、中层、底层。

顶层可以理解为进入事务处理机制这个系统的一些函数;

中层可以理解为事务块的处理,这些事务块中包含了子事务块;

底层就是内核层,是以内核视角处理子事务块中的内容。

具体的事务处理系统可以说就是一个状态机,进入系统中之后函数先判断系统什么状态,再进行处理,然后以一个状态结束,然后接下来的事务以前一个事务结束的状态运行。

1.1:顶层模块分析

1.1.1 简述:

顶层:每次要执行一个 query 语句的前后,都要调用 xact.c 文件里的事务执行函数来进行事务处理:函数有三个,分别是:

1.1.2 函数分析:

  1. StartTransactionCommand(开始事务处理函数)

很明显,进入这个 command 函数后,系统要先判断此时的状态,如果是 TBLOCK_DEFAULT 状态,也就是默认的状态,那么就运行 startTransaction 函数,这就是真正开始处理事务。如果是其他状态,则可能会回滚或者中止。

  1. CommitTransactionCommand(结束\完成事务处理函数)

这个函数,也类似,先判断 TBlockstate 的状态进行 commit 操作。

3、AbortCurrentTransaction(中止当前事务函数:回滚)

1.2:中层模块分析

1.2.1:简述:

中层:上层事务状态机控制函数:比如 begin,commint,rollback,savepoint,rollback to,release 等操作,系统就会通过 mainloop 来改变当前状态并调用状态机内其他函数。

他们在 opengauss 系统中的函数是:

1.2.2 函数分析:

具体的函数说明如下:

上层事务状态机的状态声明在代码中如下:

后缀的意思:

Begin:开始一个事务块或者子事务

Inprogress:子事务或者事务块在运行中

End:commit 命令被接受

Abort:子事务或者事务块中止,等待 rollback

Abort_End:子事务或者事务块中止,rollback 被接受

Abort_Pending:事务运行中,处于回滚等待状态

Prepare:事务运行中,prepare 命令被接受

Undo:子事务或事务块需要进行回滚

Release:子事务 release 命令被接受

Commit:子事务 commit 命令被接受

Restart:处于运行中的子事务需要被回滚

Abort_Restart:中止的子事务需要被回滚

加了 sub 代表子事务块状态。

1.3 底层模块分析

1.3.1 简述:

底层:上面说的是中层和顶层,但底层事务处理才是真正进行事务处理,以内核的视角,通过如下函数来运行。

1.3.2 函数分析:

具体说明如下:

状态如下:

和事务块的状态声明类似。

以上的那些函数都在 xact.c 文件中,我们可以举个简单的例子来说明:

  1. BEGIN

  2. SELECT * FROM foo

  3. INSERT INTO foo VALUES (...)

  4. COMMIT

一共四条指令,每条指令调用的函数如下:

其中有一个函数叫:CommitCounterIncrement,这个函数作用是允许将来的指令查看过去操作过的的指令的效果(通俗的讲就是让后来者知道前者在干什么),也是个顶层函数。另外 process 开头的函数应该都是过程中的执行操作,具体操作得看 query 语句要干什么。

这样就比较清楚的看出事务处理的层次了。即顶层->中层->底层的实现,由前一层来决定下一层要干什么。

二:事务处理机制:

2.1 子事务的处理机制:

开始执行一个子事务的时候:使用 TransactionState(子事务状态)结构堆栈实现,每个子事务都有指向它的父事务的指针,当打开一个新的子事务时,调用 PushTransaction,将创建一个新的 TransactionState(状态)这个通过 ResetUndoActionInfo 函数来实现,父链接指向当前的状态,然后调用 StartSubTransaction 函数来将新的 TransactionState(状态)定义一个合适的值,并将其他子事务的状态也更新。

StartsubTransaction 函数如下:

开始一个子事务前,需要先给它在资源管理器中进行初始化(下图中的 AtSubStart 开头的函数),然后创建一个新的状态:ResetUndoActionInfo 函数实现

Pushtransaction 函数:

结束一个子事务时:可以调用 CommitSubTransaction 或者调用 AbortsubTransaction 和 CleanupsubTransaction。然后调用 PopTransaction 将当前的子事务状态弹出并回到父事务那去。

CommitSubTransaction 函数:

很明显,通过 if 语句中的子事务的状态来决定接下来的操作。这个函数也调用函数来对 handler 和 cache 的清理。

注:堆栈结构的好处是,当我们在弹出状态堆栈顶时出错的话,剩下的堆栈条目仍能显示我们需要完成的操作。

2.2 事务提交机制

当我们首次引用一个事务或者一个子事务时,会给他标记一个 XID 用以区分,当一个事务有子事务时,先给父事务标记,再给子事务标记,来确保子事务先被处理。

这些 XID 占用 shared memory,同时也会对它们进行加锁处理,如果碰到一个没有 XID 的事务的话,一般会给它分配一个 VXID(虚拟 XID),不占用共享内存。

当事务结束后,使用 CLOG 记录是否提交,使用 CSNLOG 记录该事务提交的序列,用于可见性判断。

这是 CommitTransaction 函数中的一条,用于记录已经提交的事务 ID

RecordTransactonCommit 函数就是向 CLOG 提交记录的函数,如下:

它首先是初始化记录,然后向 XLOG(事务日志)索取要提交指令的记录的 data,然后获取事务提交序列来保证一致性和持久性,一系列操作下来决定那个传输的判定句的真值是否是真(markXidCommitted 这个参数的值是否是真),最后将记录传给 CLOG。

传输操作如下:

由此也可见 LOG 文件是以树的形式进行储存数据的。

通过 CLogSetPageStatus 函数可以看出来:

同时,我们也可以来看 CLOG 等日志文件是如何存储事务状态的。

1:首先,openGauss 中对于每个事务 id 使用 4 个 bit 位来标识它的状态。CLOG 定义代码如下:

物理组织形式如下例:

事务 1、4、5 还在运行中,事务 2 已经提交,事务 3 已经回滚。

2:openGauss 为每个事务 id 分配 8 个字节 uint64 的 CSN 号,CSNLOG 状态识别代码如下:

物理结构同 CLOG:

事务 id
2048、20492050、20512052、2053 的对应的 CSN 号依次是 5、4、7、1068;也就是说事务提交的次序依次是 2049-\>2048-\>2052-\>2050-\>2053-\>2051