1. 事务系统简介
PostgreSQL 中负责管理事务运行的模块称为事务管理器,各部分作用如下:
- 事务管理器是事务系统的中枢,由一个有限状态自动机实现,接收外部系统的命令或操作系统信息,根据当前事务的状态来决定事务的下一步状态。
- 锁管理器实现系统并发控制所需要的锁。
- 日志管理器用于记录事务执行的状态和数据的变化过程,包括事务提交日志(CLOG)和事务日志(XLOG)。其中事务提交日志CLOG只记录事务执行的结果状态,事务日志XLOG记录了数据的变化过程并保持了一定的冗余数据。
以下为简单查询语句的执行过程:
BEGIN;
SELECT * FROM TABLE_A;
END;
-
Fork一个Postgres来处理上述命令。
-
调用底层事务处理函数启动一个事务,并返回上层。
-
读取“BEGIN"语句,改变事务块状态。(后续详解)
-
进入事务底层,执行器执行该语句。
-
读取”END“语句,提交事务并退出,结束事务块。
2. 事务系统上层
在 PostgreSQL 的事务处理层次中,位于事务系统上层的是事务块。 PostgreSQL 执行一条 SQL 语 句前会调用 StartTransactionCommand 函数,执行结束时会调用 CommitTransactionCommand 函数。如果 命令执行失败,则会调用 AbortCurrentTransaction 函数。上述三个函数根据事务块的状态执行不同的 操作,并调用不同的底层事务执行函数。PostgreSQL 将事务系统分成上层(事务块)和底层(事务)两个层次,通过分层的设计,在处理上层业务的时候可以屏蔽具体细节。值得注意的是,上述三个函数只是进入事务系统上层的入口函数,并不处理具体事务。
2.1 事务块状态
typedef enum TBlockState
{
/* not-in-transaction-block states */
TBLOCK_DEFAULT, /* 缺省状态idle */
TBLOCK_STARTED, /* 执行简单查询事务running single-query transaction */
/* transaction block states */
TBLOCK_BEGIN, /* 事务开始 starting transaction block */
TBLOCK_INPROGRESS, /* 正在处理live transaction */
TBLOCK_PARALLEL_INPROGRESS, /* 正在并行处理live transaction inside parallel worker */
TBLOCK_END, /* 结束,提交后设置COMMIT received */
TBLOCK_ABORT, /* 出错,等待 ROLLBACK failed xact, awaiting ROLLBACK */
TBLOCK_ABORT_END, /* 出错,已接收 ROLLBACK failed xact, ROLLBACK received */
TBLOCK_ABORT_PENDING, /* 处理中,已接收到 ROLLBACK live xact, ROLLBACK received */
TBLOCK_PREPARE, /* 处理中,已接受到 PREPARE live xact, PREPARE received */
/* subtransaction states 子事务状态, 同上*/
TBLOCK_SUBBEGIN, /* starting a subtransaction */
TBLOCK_SUBINPROGRESS, /* live subtransaction */
TBLOCK_SUBRELEASE, /* RELEASE received */
TBLOCK_SUBCOMMIT, /* COMMIT received while TBLOCK_SUBINPROGRESS */
TBLOCK_SUBABORT, /* failed subxact, awaiting ROLLBACK */
TBLOCK_SUBABORT_END, /* failed subxact, ROLLBACK received */
TBLOCK_SUBABORT_PENDING, /* live subxact, ROLLBACK received */
TBLOCK_SUBRESTART, /* live subxact, ROLLBACK TO received */
TBLOCK_SUBABORT_RESTART /* failed subxact, ROLLBACK TO received */
} TBlockState;
2.2 事务块操作
2.2.1. 事务块基本操作
(1)StartTransactionCommand函数
(2)CommitTransactionCommand函数
(3)AbortCurrentTransaction函数
2.2.2 事务块状态改变
如下图所示,其状态改变规则是一个有限状态自动机:
由上图可知,事务块状态改变函数主要包括BeginTransactionBlock、EndTransactionBlock和UserAbortTransactionBlock函数,主要功能是根据用户输入命令以及系统状态来改变事务块状态。PostgreSQL 提交的任何一个 SQL 语句都被系统默认为一个事务块。若要自定义一个事务块的边界,则要用 BEGIN 和 END 命令来实现。 BEGIN 和 END 命令类似于一个大括号,在这个大括号中的 SQL 语句被系统认为是处于同一个事务块中的语句。
(1) 执行BEGIN命令
执行 BEGIN 命令后,系统会进入一个事务块,这里状态的改变是调用函数 BeginTransactionock 来完成的。该函数判断当前事务块状态,如果事务块状态为 TBLOCK _ STARTED ,说明当前事务还没有进入到真正的事务块中,那么把事务块状态置为 TBLOCK_BEGIN , 说明这是一个事务块的开始。如果当前事务块状态是 TBLOCK_INPROGRESS 或者 TBLOCK_SUBIN PROGRESS ,说明已经有一个事务块正在运行,这时系统会输出一个警告。对于BeginTransaction8Iock 函数来说, TBLOCK_END、 TBLOCK_ABORT_END 等状态是无效的,一旦该函数遇到这些无效状态, PostgreSQL 会抛出一个错误 。
(2) 执行END命令
PostgreSQL 在接收到 END 命令时会调用函数 EndTransactionBlock ,该函数指示一个事务块的结束。由于一个事务块的结束可能由 COMMIT 造成,也可能由 ROLLBACK 造成,所以 EndTransactionBlock 将根据返回值来确定系统的动作,返回 TRUE 表示函数成功 COMMIT,返回 FALSE 表示 ROLLBACK。 该函数的操作过程如下:
1 )判断当前事务块状态。 如果事务正在执行当中,即事务块状态为 TBLOCK_INPROGRESS, 则把事务块状态改为 TBLOCK_END ,同时把返回值置为 TRUE; 如果事务块状态为TBLOCK_SUBINPROGRESS,说明当前处于一个活动的子事务内,递归地把子事务块状态置为 TBLOCK_SUBEND, 最后把顶层事务块状态改为 TBLOCK_END ,同时把返回值置为 TRUE。
2)如果当前事务失败,即事务块状态为 TBLOCK_ABORT ,则需要退出该事务块,把事务块状态置为 TBLOCK_ABORT_END ,同时返回 FALSE; 如果当前事务块状态是TBLOCK _ SUBABORT,即我们处于失败的子事务中,这时把整个事务看成是一个失败事务,递归地把子事务块状态置为 TBLOCK_SUBABORT_END ,最后把顶层事务块状态置为 TBLOCK_ABORT_END ,同时返回 FALSE。
(3) 执行 ROLLBACK 命令
函数 UserAbortTransactionBlock 用于执行用户提交的一个 ROLLBACK 命令。如果当前事务块状态是 TBLOCK_ABORT_PENDING ,由于已经收到了用户发来的 ROLLBACK 命令,需要把事务块状态置为 TBLOCK_ABORT_PENDING 以退出该事务块。如果当前事务已经是一个失败事务,事务块状态为 TBLOCK_ABORT ,则只需把该状态置为 TBLOCK_ABORT_END 即可。
3. 事务系统底层
/*
* transaction states - transaction state from server perspective
*/
typedef enum TransState
{
TRANS_DEFAULT, /* idle */
TRANS_START, /* transaction starting */
TRANS_INPROGRESS, /* inside a valid transaction */
TRANS_COMMIT, /* commit in progress */
TRANS_ABORT, /* abort in progress */
TRANS_PREPARE /* prepare in progress */
} TransState;
/*
* transaction state structure
*/
typedef struct TransactionStateData
{
TransactionId transactionId; /* my XID, or Invalid if none */
SubTransactionId subTransactionId; /* my subxact ID */
char *name; /* savepoint name, if any */
int savepointLevel; /* savepoint level */
TransState state; /* low-level state */
TBlockState blockState; /* high-level state */
int nestingLevel; /* transaction nesting depth */
int gucNestLevel; /* GUC context nesting depth */
MemoryContext curTransactionContext; /* my xact-lifetime context */
ResourceOwner curTransactionOwner; /* my query resources */
TransactionId *childXids; /* subcommitted child XIDs, in XID order */
int nChildXids; /* # of subcommitted child XIDs */
int maxChildXids; /* allocated size of childXids[] */
Oid prevUser; /* previous CurrentUserId setting */
int prevSecContext; /* previous SecurityRestrictionContext */
bool prevXactReadOnly; /* entry-time xact r/o state */
bool startedInRecovery; /* did we start in recovery? */
bool didLogXid; /* has xid been included in WAL record? */
int parallelModeLevel; /* Enter/ExitParallelMode counter */
struct TransactionStateData *parent; /* back link to parent */
} TransactionStateData;
3.2 事务操作函数
3.2.1 启动事务
-
将全局变量CurrentTransactionState指向TopTransactionStateData,然后将底层事务状态设置为TRANS_START。
-
确保旧快照已经被释放,重置事务状态变量,并初始化事务内部计数器。
-
使用AtStart_Memory和AtStart_ResourceOwner函数分别完成内存上下文和资源跟踪器的初始化。
3.2.2 提交事务
-
检查事务状态:确保当前事务状态为TRANS_INPROGRESS,检查完事务状态之后进入预提交过程 。
-
触发所有延迟的触发器。
-
关闭大对象
-
如果修改了pg_database 等系统表,则执行更新操作并通知 Postmaster 进程。这个过程由函数 AtEOXact_UpdatePasswordFile 完成,该函数是事务真正提交前的最后一步。如果执行完 AtEOXact_UpdatePasswordFile 之后系统出现错误异常中断本事务,Postmaster可以接收到这个消息并做善后处理。
-
提交事务:把当前事务状态置为 TRANS_COMMIT 了,并执行 RecordTransactionCommit 函数,用于将当前所提交的事务所产生的日志写 回磁盘。返回最后处理完的当前事务或子事务的XID,如果当前事务没有XlD,则返回 lnvalidTransactionld
-
清理操作。先释放其他后台进程可见的资源(如文件、缓冲区锁等) ,接着释放锁,再释放进程的本地资源 。
-
恢复初始状态:设置事务状态为缺省状态 TRANS_DEFAULT ,然后准备接受下一事务。
3.2.3 退出事务
-
关中断,因为进行事务退出后的清理工作时不能被别的事件打断。
-
确保内存上下文和资源跟踪器有效。
-
释放所有拥有的轻量锁。
-
检查事务状态 。 确保当前事务状态为 TRANS_INPROGRESS 或 TRANS_PREPARE 。
-
终止执行事务。
-
记录日志。
-
清理资源。
-
恢复中断。
3.2.4 清理事务
-
判断当前事务状态是否为 TRANS_ABORT 并释放Portal内存。
-
释放 ResourceOwner、MemoryContext,并把事务状态恢复到默认情况。
3.3 查询事务执行过程实例
以下为简单查询语句的例子。
BEGIN;
SELECT * FROM TABLE_A;
END;
BEGIN
-
事务块状态为TBLOCK_DEFAULT。
-
调用 StartTransactionCommand 函数。由于当前事务块状态为 TBLOCK_DEFAULT。于是调用 StartTransaction 函数,首先设置事务状态为TRANS_START,然后完成有关内存、缓冲区、锁的处理后设置事务状态为 TRANS_INPROGRESS。最后设置事务块状态为 TBLOCK_STARTED。
-
调用 processUtility 函数处理 BEGlN语句.
-
遇到 " BEGlN" ,所以调用BeginTransactionBlock 函数处理,它将设置事务块状态为 TBLOCK_BEGlN。
-
最后调用 CommitTransactionCommand 函数,由于当前事务块状态为 TBLOCK_BEGIN,于是改变事务块状态为TBLOCK_lNPROGRESS,并准备读取下一条命令 。
SELECT
-
调用StartTransactionCommand函数,由于当前事务块状态为 TBLOGK_INPROGRESS,表明正在继续该事务的命令,所以直接返回。
-
调用ProcessQuery 函数进入执行器处理 SELECT 语句。
-
调用 EndTransactionBlock函数,返回并准备继续读下一个命令。
END
-
调用 StartTransactionCommand 函数,当前事务块状态为 TBLOCK_lNPROGRESS,表明正在继续该事务的命令,所以直接返回 。
-
调用ProcessUtility 函数处理 END 语句 。
-
调用 commitTransactionBlock 函数并设置事务块状态为 TBLOCK_END。
-
调用 CommitTransactionCommand 函数,由于当前事务块状态为 TBLOCK_END ,于是调用 CommitTransaction 函数提交事务,首先设置事务状态为 TRANS_COMMIT, 然后真正提交事务,并清理关资源.。等着一切结束后,设置事务状态为 TRANS_DEFAULT 并返回。最后设置事务块状态为 TBLOCK_DEFAULT,整个事务执行结束。
4. 事务保存点和子事务
BEGIN Transantion S;
insert into table1 values(1);
SAVEPOINT P1;
insert into table1 values(2);
SAVEPOINT P2;
insert into table1 values(3);
SAVEPOINT P3;
ROLLBACK TO savepoint P1;
COMMIT;
// 执行结果:向表table1中插入数据1
4.1 保存点实现原理
PostgreSQL 在把 SAVEPOINT 认为是定义子事务的标记。事务状态数据结构 TransactionStaleData中有如下变量:
struct TransactionStateData*parent; /*指向父事务的指针*/
parent 指针指向本事务的父事务,上例中事务的层次结构如下所示:
子事务的层次描述用一个链栈实现。栈顶元素拥有一个指向其父事务的指针。当启动一个新的子事务时,系统调用PushTransaction 函数把描述该子事务的 TransaclionStale 结构变量压入战中,这 个变量可以标识该事务。相应的. PopTransaclion 函数的功能是把钱顶事务弹出。 PushTransaclion 函数为子事务创建一个 TransactionState 并压人事务状态堆栈中。在函数执行过程中, CurrentTransactionState 会被切换到新创建的事务状态。PopTransaction 函数将当前事务状态弹出堆栈,把 CurrentTransactionState 切换到父事务的事务状态并转换资源所有者、以及事务内存上 下文。 保存点的作用主要就是实现事务的回滚,即从当前子事务回滚到该事务链中的某个祖先事务。 在定义一个保存点时,需要用户输入保存点名称(例如 P1) .从而标识当前子事务。在用户执行了一系列子事务之后回滚到该子事务时( ROLLBACK TO P1) .会调用 Rollback ToSavepoint 函数在事务链中从当前事务节点回溯查找该保存点名称,若找到,就直接把 CurrentTransactionState 指针移到查找到的事务节点处。由于 PostgreSQL中MemeoyContext 的存在,这里并不需要释放抛弃的子事务的相关内存资源,释放资源的操作在最后事务提交或者退出时通过 MemoryContext来实现 。