海山数据库(He3DB)源码详解:事务块执行过程及内核函数
本文介绍了事务执行过程中,从BEGIN事务开始到事务结束END/ABORT的内核函数执行过程。
前言:
事务SQL语句的执行过程,会先通过事务块函数修改事务块状态,然后根据事务块状态执行不同的事务行为。因此,本章会讲解事务块的开始和结束过程的事务块函数。
事务块控制事务执行的过程:
在讲具体函数之前,有必要在此讲解一下,事务块是如何控制事务行为的。事务管理器中,事务块会根据当前事务状态,来控制具体的事务操作行为,具体流程如下:
- 开始事务块:表示一个新的事务块的开始。
- 检查事务状态:检查当前事务的状态,是否活跃、空闲或处于错误状态。
- 活跃:如果事务处于活跃状态,执行事务操作。
- 执行事务操作:执行具体的事务操作,如插入、更新或删除数据。
- 空闲:如果事务处于空闲状态,等待操作命令。
- 等待操作命令:在事务空闲时等待接收到新的操作命令。
- 接收到操作命令:一旦接收到操作命令,开始执行事务操作。
- 执行事务操作:执行具体的事务操作,如插入、更新或删除数据。
- 操作成功?:检查事务操作是否成功。
- 提交事务:如果操作成功,提交事务。
- 回滚事务:如果操作失败或事务处于错误状态,回滚事务。
- 错误:如果事务处于错误状态,进行回滚操作。
- 回滚事务:如果操作失败或事务处于错误状态,回滚事务。
- 活跃:如果事务处于活跃状态,执行事务操作。
- 结束事务块:表示事务块的结束,此时事务已经提交或回滚。
如何进行事务块操作:
使用 BEGIN
命令可以手动控制启动事务的边界,开始一个新的事务块;
使用 END \COMMIT
命令可以手动控制结束事务,结束一个执行中的事务块;
使用 ABORT \ ROLLBACK
命令可以手动控制放弃回滚事务,结束一个出错的事务块;
当执行这些SQL语句时,系统底层会调用相对应的事务块函数,来修改当前事务块的状态,从而控制具体的事务操作行为。其中,事务块操作的开始和结束分别对应的函数为:
BEGIN
对应的事务块函数为BeginTransactionBlock()
函数;END \ COMMIT
对应的事务块函数为EndTransactionBlock()
函数;ABORT \ ROLLBACK
对应的事务块函数为UserAbortTransactionBlock()
函数。
那么,这些函数的执行流程是什么样的?
BeginTransactionBlock()函数的执行过程:
当终端输入BEGIN或者START TRANSACTION命令时,会开始一个事务块。处理过程首先会调用BeginTransactionBlock()函数。该函数内部主体为一个switch结构,会对枚举类型TBlockState中的所有状态都进行一个判断,并作一个对应的处理。执行过程示意图如下:
-
首先通过全局变量
CurrentTransactionState
获得当前事务块状态的变量;TransactionState s = CurrentTransactionState;
-
然后通过
switch-case
结构,根据当前事务块状态做对应的操作,其中有包括:
- 如果当前事务块状态为TBLOCK_STARTED,则修改为TBLOCK_BEGIN;
case TBLOCK_STARTED: s->blockState = TBLOCK_BEGIN; break;
- 如果当前事务块状态为TBLOCK_IMPLICIT_INPROGRESS,则修改为TBLOCK_BEGIN;
case TBLOCK_IMPLICIT_INPROGRESS: s->blockState = TBLOCK_BEGIN; break;
- 如果当前事务块状态为TBLOCK_INPROGRESS、TBLOCK_PARALLEL_INPROGRESS、TBLOCK_SUBINPROGRESS、TBLOCK_ABORT和TBLOCK_SUBABORT,会进行WARNING报警,但是不会影响执行过程,可以继续执行;
case TBLOCK_INPROGRESS: case TBLOCK_PARALLEL_INPROGRESS: case TBLOCK_SUBINPROGRESS: case TBLOCK_ABORT: case TBLOCK_SUBABORT: ereport(WARNING, (errcode(ERRCODE_ACTIVE_SQL_TRANSACTION), errmsg("there is already a transaction in progress"))); break;
- 如果当前事务块状态为上述提到的其他状态,则直接报FATAL错误,直接退出事务块过程;
case TBLOCK_DEFAULT: case TBLOCK_BEGIN: case TBLOCK_SUBBEGIN: case TBLOCK_END: case TBLOCK_SUBRELEASE: case TBLOCK_SUBCOMMIT: case TBLOCK_ABORT_END: case TBLOCK_SUBABORT_END: case TBLOCK_ABORT_PENDING: case TBLOCK_SUBABORT_PENDING: case TBLOCK_SUBRESTART: case TBLOCK_SUBABORT_RESTART: case TBLOCK_PREPARE: elog(FATAL, "BeginTransactionBlock: unexpected state %s", BlockStateAsString(s->blockState)); break;
以上,就是BeginTransactionBlock()函数的全部执行过程。
EndTransactionBlock()函数的执行过程:
当终端输入COMMIT或者EDN语句时,这时一个事务块会发起提交或者结束,如果输入END,正确结束为提交,错误结束为回滚。处理过程首先会调用EndTransactionBlock()函数,并根据返回值判断是否时错误结束发生回滚。EndTransactionBlock()函数和BeginTransactionBlock()十分相似,内部主要是一个swith结构,这里直接说执行流程:
-
首先通过全局变量
CurrentTransactionState
获得当前事务块状态的变量,并定义返回值bool变量result;TransactionState s = CurrentTransactionState; bool result = false;
-
然后通过
switch-case
结构,根据当前事务块状态做对应的操作,其中有包括:
- 如果当前事务块状态为TBLOCK_INPROGRESS,则修改为TBLOCK_END,并将变量result赋值为true;
case TBLOCK_INPROGRESS: s->blockState = TBLOCK_END; result = true; break;
- 如果当前事务块状态为TBLOCK_IMPLICIT_INPROGRESS,表示当前处在一个隐式的事务进程模式。判断入参chain的值,如果为true,表示该状态下存在子事务,报Error错误,退出事务执行过程;否则报Warning,修改事务块状态为TBLOCK_END,并将变量result赋值为true;
case TBLOCK_IMPLICIT_INPROGRESS: if (chain) ereport(ERROR, (errcode(ERRCODE_NO_ACTIVE_SQL_TRANSACTION), /* translator: %s represents an SQL statement name */ errmsg("%s can only be used in transaction blocks", "COMMIT AND CHAIN"))); else ereport(WARNING, (errcode(ERRCODE_NO_ACTIVE_SQL_TRANSACTION), errmsg("there is no transaction in progress"))); s->blockState = TBLOCK_END; result = true; break;
- 如果当前事务块状态为TBLOCK_ABORT,只修改事务块状态为TBLOCK_ABORT_END;
case TBLOCK_ABORT: s->blockState = TBLOCK_ABORT_END; break;
- 如果当前事务块状态为TBLOCK_SUBINPROGRESS,表示当前事务属于正常状态可以提交,需要从当前节点逐级回溯到父节点,并修改过程中每个节点的状态;
case TBLOCK_SUBINPROGRESS: while (s->parent != NULL) { if (s->blockState == TBLOCK_SUBINPROGRESS) s->blockState = TBLOCK_SUBCOMMIT; else elog(FATAL, "EndTransactionBlock: unexpected state %s", BlockStateAsString(s->blockState)); s = s->parent; } if (s->blockState == TBLOCK_INPROGRESS) s->blockState = TBLOCK_END; else elog(FATAL, "EndTransactionBlock: unexpected state %s", BlockStateAsString(s->blockState)); result = true; break;
- 首先,通过while循环,回溯每个节点,并对每个节点的事务块状态进行判断。如果是TBLOCK_SUBINPROGRESS,则修改为TBLOCK_SUBCOMMIT,由后续的CommitSubTransaction()函数中进行提交,如果是其他,则直接报FATAL错误,退出事务操作过程;
- 退出while后,判断父节点事务块状态,如果是TBLOCK_INPROGRESS,则修改为TBLOCK_END,表示事务完成,后续由CommitTransaction()函数完成具体的事务提交,如果是其他,报FATAL错误;
- 最后修改返回值result为true。
- 如果当前事务块状态为TBLOCK_SUBABORT,表示当前事务整体处于放弃状态,需要从当前节点逐级回溯到父节点,并修改过程中每个节点的状态以实现整体事务的放弃;
case TBLOCK_SUBABORT: while (s->parent != NULL) { if (s->blockState == TBLOCK_SUBINPROGRESS) s->blockState = TBLOCK_SUBABORT_PENDING; else if (s->blockState == TBLOCK_SUBABORT) s->blockState = TBLOCK_SUBABORT_END; else elog(FATAL, "EndTransactionBlock: unexpected state %s", BlockStateAsString(s->blockState)); s = s->parent; } if (s->blockState == TBLOCK_INPROGRESS) s->blockState = TBLOCK_ABORT_PENDING; else if (s->blockState == TBLOCK_ABORT) s->blockState = TBLOCK_ABORT_END; else elog(FATAL, "EndTransactionBlock: unexpected state %s", BlockStateAsString(s->blockState)); break;
- 首先,通过while循环,回溯每个节点,并对每个节点的事务块状态进行判断。如果是TBLOCK_SUBINPROGRESS,则修改为TBLOCK_SUBABORT_PENDING,表示处于准备放弃的状态,后续会通过AbortSubTransaction()和CleanupSubTransaction()函数处理;如果是TBLOCK_SUBABORT,则修改为TBLOCK_SUBABORT_END,表示处于结束放弃的状态,已经由AbortSubTransaction()函数处理过,后续只需要通过CleanupSubTransaction()清理相关资源即可;如果是其他,则直接报FATAL错误,退出事务操作过程;
- 退出while后,判断父节点事务块状态,如果是TBLOCK_INPROGRESS,则修改为TBLOCK_ABORT_PENDING,表示事务准备放弃,后续由AbortTransaction()和CleanupTransaction()函数处理;如果是TBLOCK_ABORT,则修改为TBLOCK_ABORT_END,表示处于结束放弃的状态,已经由AbortTransaction()函数处理过,后续只需要通过CleanupTransaction()清理相关资源即可;如果是其他,报FATAL错误。
- 如果当前事务块状态为TBLOCK_STARTED,表示当前事务执行出错,需要通过chain判断错误的严重性。判断入参chain的值,如果为true,表示当前状态下存在子事务,报Error错误,退出事务执行过程;否则报Warning,并将变量result赋值为true;
if (chain) ereport(ERROR, (errcode(ERRCODE_NO_ACTIVE_SQL_TRANSACTION), /* translator: %s represents an SQL statement name */ errmsg("%s can only be used in transaction blocks", "COMMIT AND CHAIN"))); else ereport(WARNING, (errcode(ERRCODE_NO_ACTIVE_SQL_TRANSACTION), errmsg("there is no transaction in progress"))); result = true; break;
- 如果当前事务块状态为TBLOCK_PARALLEL_INPROGRESS,表示当前事务处于并行操作的状态,不能有parallel worker进行提交,报FATAL错误,直接退出事务操作过程;
case TBLOCK_PARALLEL_INPROGRESS: ereport(FATAL, (errcode(ERRCODE_INVALID_TRANSACTION_STATE), errmsg("cannot commit during a parallel operation"))); break;
- 如果处于其他状态,则表示当前事务状态出现预料之外的情况,报FATAL错误,直接退出事务操作过程。
/* These cases are invalid. */ case TBLOCK_DEFAULT: case TBLOCK_BEGIN: case TBLOCK_SUBBEGIN: case TBLOCK_END: case TBLOCK_SUBRELEASE: case TBLOCK_SUBCOMMIT: case TBLOCK_ABORT_END: case TBLOCK_SUBABORT_END: case TBLOCK_ABORT_PENDING: case TBLOCK_SUBABORT_PENDING: case TBLOCK_SUBRESTART: case TBLOCK_SUBABORT_RESTART: case TBLOCK_PREPARE: elog(FATAL, "EndTransactionBlock: unexpected state %s", BlockStateAsString(s->blockState)); break;
UserAbortTransactionBlock()函数执行过程:
当终端输入ROLLBACK语句时,这时会发起一个事务的回滚操作,放弃之前的所有操作,数据库回滚到事务开始之前的样子。通过调用UserAbortTransactionBlock()函数执行该过程。该函数和其他事务块操作函数十分相似,内部主要是一个swith结构,这里直接说执行流程:
- 首先通过全局变量
CurrentTransactionState
获得当前事务块状态的变量;
TransactionState s = CurrentTransactionState;
- 然后通过
switch-case
结构,根据当前事务块状态做对应的操作,其中有包括:
- 如果事务块处在TBLOCK_INPROGRESS状态下,将状态修改为TBLOCK_ABORT_PENDING;
case TBLOCK_INPROGRESS:
s->blockState = TBLOCK_ABORT_PENDING;
break;
- 如果事务块处在TBLOCK_ABORT状态下,将状态修改为TBLOCK_ABORT_END;
case TBLOCK_ABORT:
s->blockState = TBLOCK_ABORT_END;
break;
- 如果事务块处在TBLOCK_SUBINPROGRESS或TBLOCK_SUBABORT,标记每个子事务状态为放弃状态,直到top事务位置;
case TBLOCK_SUBINPROGRESS:
case TBLOCK_SUBABORT:
while (s->parent != NULL)
{
if (s->blockState == TBLOCK_SUBINPROGRESS)
s->blockState = TBLOCK_SUBABORT_PENDING;
else if (s->blockState == TBLOCK_SUBABORT)
s->blockState = TBLOCK_SUBABORT_END;
else
elog(FATAL, "UserAbortTransactionBlock: unexpected state %s",
BlockStateAsString(s->blockState));
s = s->parent;
}
if (s->blockState == TBLOCK_INPROGRESS)
s->blockState = TBLOCK_ABORT_PENDING;
else if (s->blockState == TBLOCK_ABORT)
s->blockState = TBLOCK_ABORT_END;
else
elog(FATAL, "UserAbortTransactionBlock: unexpected state %s",
BlockStateAsString(s->blockState));
break;
-
循环检查当前节点的父节点状态,如果不为空,则根据blockState值对应修改当前事务的事务块状态,并将事务指针指向父节点;
-
当循环到top节点时,退出循环,并根据top节点的blockState值对应修改当前top事务的事务块状态。
-
如果事务块处在TBLOCK_STARTED或TBLOCK_IMPLICIT_INPROGRESS,根据chain的值报ERROR或者WARING错误,并将事务块状态修改为TBLOCK_ABORT_PENDING;
case TBLOCK_STARTED:
case TBLOCK_IMPLICIT_INPROGRESS:
if (chain)
ereport(ERROR,
(errcode(ERRCODE_NO_ACTIVE_SQL_TRANSACTION),
/* translator: %s represents an SQL statement name */
errmsg("%s can only be used in transaction blocks",
"ROLLBACK AND CHAIN")));
else
ereport(WARNING,
(errcode(ERRCODE_NO_ACTIVE_SQL_TRANSACTION),
errmsg("there is no transaction in progress")));
s->blockState = TBLOCK_ABORT_PENDING;
break;
-
如果chain为true,表示事务的链式执行,事务在结束后会开启一个新事务,如果开启则汇报ERROR错误;
-
如果chain为false,报WARIING错误,修改事务块状态。
-
如果务块处在TBLOCK_PARALLEL_INPROGRESS,则直接报FATAL错误。
case TBLOCK_PARALLEL_INPROGRESS:
ereport(FATAL,
(errcode(ERRCODE_INVALID_TRANSACTION_STATE),
errmsg("cannot abort during a parallel operation")));
break;
原因:事务管理系统设计上避免在并行执行的事务中执行 ABORT,以维护系统的稳定性和数据的一致性。这种设计确保了并行查询的性能和可靠性。
- 如果事务块处于其他状态,直接FATAL。
case other_TBLOCK:
elog(FATAL, "UserAbortTransactionBlock: unexpected state %s",
BlockStateAsString(s->blockState));
break;
- 对当前事务块状态做判断,并保存chain的值到
Assert(s->blockState == TBLOCK_ABORT_END ||
s->blockState == TBLOCK_ABORT_PENDING);
s->chain = chain;
统设计上避免在并行执行的事务中执行 ABORT,以维护系统的稳定性和数据的一致性。这种设计确保了并行查询的性能和可靠性。
- 如果事务块处于其他状态,直接FATAL。
```C
case other_TBLOCK:
elog(FATAL, "UserAbortTransactionBlock: unexpected state %s",
BlockStateAsString(s->blockState));
break;
- 对当前事务块状态做判断,并保存chain的值到
Assert(s->blockState == TBLOCK_ABORT_END ||
s->blockState == TBLOCK_ABORT_PENDING);
s->chain = chain;