openGauss
开源数据库
openGauss社区官网
开源社区
openGauss事务管理系统分析
Opengauss 事务管理系统分析
一、概述:
opengauss 的事务处理系统一共有三层:顶层、中层、底层。
顶层可以理解为进入事务处理机制这个系统的一些函数;
中层可以理解为事务块的处理,这些事务块中包含了子事务块;
底层就是内核层,是以内核视角处理子事务块中的内容。
具体的事务处理系统可以说就是一个状态机,进入系统中之后函数先判断系统什么状态,再进行处理,然后以一个状态结束,然后接下来的事务以前一个事务结束的状态运行。
1.1:顶层模块分析
1.1.1 简述:
顶层:每次要执行一个 query 语句的前后,都要调用 xact.c 文件里的事务执行函数来进行事务处理:函数有三个,分别是:
1.1.2 函数分析:
- StartTransactionCommand(开始事务处理函数)
很明显,进入这个 command 函数后,系统要先判断此时的状态,如果是 TBLOCK_DEFAULT 状态,也就是默认的状态,那么就运行 startTransaction 函数,这就是真正开始处理事务。如果是其他状态,则可能会回滚或者中止。
- 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 文件中,我们可以举个简单的例子来说明:
BEGIN
SELECT * FROM foo
INSERT INTO foo VALUES (...)
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、2049、2050、2051、2052、2053 的对应的 CSN 号依次是 5、4、7、10、6、8;也就是说事务提交的次序依次是 2049-\>2048-\>2052-\>2050-\>2053-\>2051