海山数据库(He3DB)源码详解:子事务Push和Pop函数

54 阅读5分钟

@[toc]

海山数据库(He3DB)源码详解:子事务Push和Pop函数

1. 执行条件

  1. 在终端中,用户执行SAVEPOINT语句时,会调用底层的DefineSavepoint函数,对处在TBLOCK_INPROGRESS或TBLOCK_SUBINPROGRESS的事务块,调用PushTransaction函数,构建事务链栈,进行子事务的一个创建。
  2. 在终端中,用户执行ROLLBACK TO语句时,会调用底层的CleanupSubTransaction函数,对需要被Abort的子事务在链栈上进行Pop操作,调用PopTransaction函数弹出链栈上Abort掉的子事务。

2. 实现过程示意图

链栈(List-based stack)实现子事务的创建和销毁通常涉及到将子事务以链表的形式组织起来,每个子事务作为一个节点,其中包含了子事务的相关信息,如子事务的 ID、状态、资源等。链栈的顶部通常指向当前活跃的子事务。

以下是两个示意图,展示链栈是如何实现子事务的创建和销毁的:

1、子事务的创建过程

image-1.png 子事务创建过程:

  1. 开始:创建子事务的过程开始。
  2. 当前事务(父事务):确定当前活跃的父事务。
  3. 链栈顶部:找到链栈的顶部,这代表了当前活跃的子事务。
  4. 新的子事务节点:创建一个新的节点,代表新的子事务。
  5. 子事务信息:在新的节点中填充子事务的相关信息。
  6. 链栈更新,指向新的子事务:更新链栈的顶部,使其指向新的子事务节点。
  7. 子事务创建完成:子事务创建过程完成。

2、子事务的销毁过程

image-2.png 子事务销毁过程:

  1. 开始:销毁子事务的过程开始。
  2. 当前子事务:确定当前要销毁的子事务。
  3. 链栈顶部:找到链栈的顶部,这代表了要销毁的子事务节点。
  4. 移除当前子事务节点:从链栈中移除当前的子事务节点。
  5. 释放子事务资源:释放与子事务相关的所有资源。
  6. 栈为空?:检查链栈是否为空,即是否还有未销毁的子事务。
  7. 指向父事务:如果栈为空,说明所有子事务都已销毁,链栈的顶部指向父事务。
  8. 指向新的栈顶子事务:如果栈不为空,链栈的顶部指向新的栈顶子事务。
  9. 子事务销毁完成:子事务销毁过程完成。

3. PushTransaction函数的执行过程

子事务的创建过程会PushTransaction函数,构建新的子事务节点并推入链栈顶部。

1、构建状态变量

获取当前状态变量为父节点,创建新的子节点状态变量并申请对应的内存。

    TransactionState p = CurrentTransactionState;
	TransactionState s;
	/*
	 * We keep subtransaction state nodes in TopTransactionContext.
	 */
	s = (TransactionState)
		MemoryContextAllocZero(TopTransactionContext,
							   sizeof(TransactionStateData));
  1. 通过全局变量CurrentTransactionState,获得当前事务的状态变量并设置为父节点p;
  2. 创建一个新的状态变量s并从TopTransactionContext中申请需要的内存。

2、获取子事务ID

子事务ID全局变量currentSubTransactionId+1,并判断子事务ID是否正确。

	/*
	 * Assign a subtransaction ID, watching out for counter wraparound.
	 */
	currentSubTransactionId += 1;
	if (currentSubTransactionId == InvalidSubTransactionId)
	{
		currentSubTransactionId -= 1;
		pfree(s);
		ereport(ERROR,
			(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
			 errmsg("cannot have more than 2^32-1 subtransactions in a
			 transaction")));
	}

如果currentSubTransactionId加1之后,依旧等于InvalidSubTransactionId(值为0),说明当前子事务ID无效,子事务创建是被,释放新申请的事务状态空间并报错。

3、初始化子事务状态

初始化子事务状态结构体中的各个参数值,并将新的子事务作为当前全局事务状态。

	/*
	 * We can now stack a minimally valid subtransaction without fear of
	 * failure.
	 */
	s->fullTransactionId = InvalidFullTransactionId;	/* until assigned */
	s->subTransactionId = currentSubTransactionId;
	s->parent = p;
	s->nestingLevel = p->nestingLevel + 1;
	s->gucNestLevel = NewGUCNestLevel();
	s->savepointLevel = p->savepointLevel;
	s->state = TRANS_DEFAULT;
	s->blockState = TBLOCK_SUBBEGIN;
	GetUserIdAndSecContext(&s->prevUser, &s->prevSecContext);
	s->prevXactReadOnly = XactReadOnly;
	s->parallelModeLevel = 0;
	s->topXidLogged = false;

	CurrentTransactionState = s;

子事务创建成功以后,将当前全局变量CurrentTransactionState赋值为新的子事务,并置于事务链栈的最顶端。

3. PopTransaction函数的执行过程

子事务的销毁过程会PopTransaction函数,弹出子事务节点并回收其内存空间。

1、获取并坚持状态变量

获取当前事务的状态变量,并坚持事务状态和父节点情况。

	TransactionState s = CurrentTransactionState;
	if (s->state != TRANS_DEFAULT)
		elog(WARNING, "PopTransaction while in %s state",
			 TransStateAsString(s->state));
	if (s->parent == NULL)
		elog(FATAL, "PopTransaction with no parent");

因为是要弹出事务,所以,首先要检查当前事务的状态,之后再检查子事务的父节点状态。

2、修改全局状态变量

对事务状态、上下文、资源拥有者相关的全局变量做修改。

 	CurrentTransactionState = s->parent;
	/* Let's just make sure CurTransactionContext is good */
	CurTransactionContext = s->parent->curTransactionContext;
	MemoryContextSwitchTo(CurTransactionContext);
	/* Ditto for ResourceOwner links */
	CurTransactionResourceOwner = s->parent->curTransactionOwner;
	CurrentResourceOwner = s->parent->curTransactionOwner;
  1. 将事务状态、上下文、资源拥有者相关的全局变量修改为父节点对应的值;
  2. 切换当前上下文到父节点的上下文中。

3、完成子事务节点销毁

释放旧的子节点结构体内存空间

	/* Free the old child structure */
	if (s->name)
		pfree(s->name);
	pfree(s);
  1. 注意最后要先释放name,因为name变量由MemoryContextStrdup函数申请分配的内存; 由于这里子节点s是局部变量,对应的空间被释放之后本身不需要管理,随着函数退出自动消失。

作者介绍

李超,移动云数据库工程师,负责云原生数据库He3DB的研发。