#pg内核
一 概述
事务管理系统
事务管理系统一般是指数据库中事务处理相关的几个模块的统称。事务管理系统主要包括三个大模块: 事务管理器、锁管理器和日志管理器。
ACID
- 原子性(Atomic)
事务必须是原子操作,要么全部成功,要么全部失败。 - 一致性(Consistent)
事务在完成时,所有的数据必须保持一致状态。 - 隔离性(Isolation)
由并发事务所作的修改必须与其他并发事务所作的修改隔离,互不影响。 - 持久性(Duration)
事务完成后,对数据库中数据的变更时永久的,不会被回滚。
二 事务管理器
事务块
pg是通过事务块实现数据库内部底层事务与终端命令的交互的。postmaster进程在接收到一个用户的连接请求后,会fork出一个子进程postgres来专门处理对应的用户请求。每一个postgres进程就可以看作是一个事务块,进程内自始至终只会有一个事务块。而显式的BEGIN、END等命令只是修改事务块的状态。
事务块与事务的区别:
- 事务块:一个进程只有一个,SQL命令只会修改其状态。
- 事务: 默认情况下,单个SQL就是一个事务,当然,也可以使用BEGIN,END显式的将多个SQL放到一个事务中。
事务块状态
在一个事务块中,SQL命令只会修改事务块的状态,其主要的状态定义在TBlockState
一个事务块默认的状态是TBLOCK_DEFAULT, 当事务开始时,会修改为不同的状态,当事务结束后会将事务块状态重置为TBLOCK_DEFAULT。
- TBlockState
typedef enum TBlockState
{
/* not-in-transaction-block states */
TBLOCK_DEFAULT, /* 空闲状态 */
TBLOCK_STARTED, /* 开始状态*/
/* transaction block states */
TBLOCK_BEGIN, /* BENGIN状态,执行BEGIN命令会进入此状态 */
TBLOCK_INPROGRESS, /* 运行中 */
TBLOCK_IMPLICIT_INPROGRESS, /* 隐式事务块运行中(已废弃) */
TBLOCK_PARALLEL_INPROGRESS, /* 并行事务运行中 */
TBLOCK_END, /* 已经提交,执行COMMIT命令会进入此状态 */
TBLOCK_ABORT, /* 事务块终止,等待回滚 */
TBLOCK_ABORT_END, /* 事务块终止,执行ROLLBACK后进入此状态 */
TBLOCK_ABORT_PENDING, /* 事务块终止中,执行ROLLBACK后可能会进入此状态 */
TBLOCK_PREPARE, /* 两阶段提交状态,执行PREPARE进入此状态 */
/* subtransaction states */
TBLOCK_SUBBEGIN, /* 子事务启动 */
TBLOCK_SUBINPROGRESS, /* 子进程运行中 */
TBLOCK_SUBRELEASE, /* 子进程释放 */
TBLOCK_SUBCOMMIT, /* 子进程提交 */
TBLOCK_SUBABORT, /* 子事务终止,等待回滚*/
TBLOCK_SUBABORT_END, /* 子事务终止,等待回滚*/
TBLOCK_SUBABORT_PENDING, /* 子事务终止,但是已经收到ROLLBACK*/
TBLOCK_SUBRESTART, /* 子事务提交,已经收到COMMIT*/
TBLOCK_SUBABORT_RESTART /* 子事务终止,已经收到ROLLBACK */
} TBlockState;
- 状态图
- 状态流转时序图
隐式事务与隐式事务块
- 隐式事务
隐式事务是指在执行SQL语句时,如果没有显示的开启事务,则系统会为该语句自动开启一个事务。每条SQL语句都是一个事务。 - 隐式事务块
隐式事务块是指在执行多个SQL语句时,如果没有显式的开启事务,则这些语句会自动组成一个事务块,也就是说系统会将这些语句看做一个整体 ,在执行过程中如果某个语句执行失败,则整个事务块都会被回滚。旧版本可以通过SET IMPLICIT_TRANSACTION ON;语句显式开启,但是当前的版本已经没有隐式事务块的概念了,只不过事务块状态中仍保留而已。
事务块基本操作函数
事务块系统一般分为三层:上层、中层、底层
在数据库中,任何语句的执行都是通过事务块的入口函数进入并开始执行的,执行完毕后通过事务块的出口函数退出,出错时通过事务块的出错函数处理,事务块的状态时通过上层函数和中层函数共同控制的,而底层函数则是主要处理事务的函数并且控制事务的状态。
上层函数
就是可通过SQL命令控制事务块的状态的变更的函数,但是不处理具体的事务。如BEGIN、END、ROLLBACK、ABORT、PREPARE、SAVEPOINT、RELEASE等命令。主要包含以下函数:
BeginTransactionBlock
EndTransactionBlock
UserAbortTransactionBlock
DefineSavepoint
ReleaseSavepoint
RollbackToSavepoint
PrepareTransactionBlock
中层函数
任何一个SQL命令的执行,都会执行中层函数。中层函数只会修改事务块的状态,不进行实际操作,主要包含:
StartTransactionCommand
CommitTransactionCommand
AbortCurrentTransaction
底层函数
实际执行事务的函数
StartTransaction
CommitTransaction
AbortTransaction
CleanupTransaction
StartSubTransaction
CommitSubTransaction
AbortSubTransaction
CleanupSubTransaction
PushTransaction
PopTransaction
SQL示例
下面通过一个SQL,分析事务块各函数的调用情况
- SQL语句
BEGIN;
SELECT * FROM foo;
INSERT INTO foo VALUES (...);
COMMIT;
- 函数调用情况
可以看到每个SQL语句都是包含在中层函数中的(StartTransactionCommand/CommitTransactionCommand)
实际的SQL对应的执行时通过Process*函数调用相应的底层函数的
/ StartTransactionCommand;
/ StartTransaction;
1) < ProcessUtility; << BEGIN
\ BeginTransactionBlock;
\ CommitTransactionCommand;
/ StartTransactionCommand;
2) / PortalRunSelect; << SELECT ...
\ CommitTransactionCommand;
\ CommandCounterIncrement;
/ StartTransactionCommand;
3) / ProcessQuery; << INSERT ...
\ CommitTransactionCommand;
\ CommandCounterIncrement;
/ StartTransactionCommand;
/ ProcessUtility; << COMMIT
4) < EndTransactionBlock;
\ CommitTransactionCommand;
\ CommitTransaction;
事务状态结构体
每个事务都会由一个事务状态结构体来表示,具体的结构如下
typedef struct TransactionStateData
{
FullTransactionId fullTransactionId; /* 事务ID 64位 */
SubTransactionId subTransactionId; /* 子事务ID */
char *name; /* 保存点名称 */
int savepointLevel; /* 保存点嵌套层次 */
TransState state; /* 事务状态 */
TBlockState blockState; /* 事务块状态 */
int nestingLevel; /* 事务嵌套深度 */
int gucNestLevel; /* GUC嵌套深度 */
MemoryContext curTransactionContext; /* 当前事务的内存上下文 */
ResourceOwner curTransactionOwner; /* 当前事务的resourceowner信息 */
TransactionId *childXids; /* 子事务ID信息r */
int nChildXids; /* 已经提交的子事务数量 */
int maxChildXids; /* childXid分配的最大size */
Oid prevUser; /* 先前的用户OID */
int prevSecContext; /* 先前的用户上下文设置标记 */
bool prevXactReadOnly; /* 先前的只读事务标记 */
bool startedInRecovery; /* 是否是恢复模式标记 */
bool didLogXid; /* XID是否被包含在XLOG日志中*/
int parallelModeLevel; /* 并行模式开关 */
bool chain; /* 当前的block结束后,是否启动一个新的block */
bool assigned; /* 父事务是否分配事务ID */
struct TransactionStateData *parent; /* 指向父事务t */
} TransactionStateData;
- fullTransactionId
每个事务都会由一个全局事务ID来表示,由fullTransactionId记录,fullTransactionId是一个64位整数,但是目前只用了32位来保存事务ID,事务ID的范围为1~2^32,是数据库非常宝贵的资源,所以不是每一个事务都会分配事务ID的,只有在进行IUD操作时,才会分配事务ID,普通事务只会分配一个虚拟事务ID。事务ID达到一定数量后,回触发数据库的冻结,以避免数据库的回卷。以后会讲到事务ID的分配和回卷。 - subTransactionId
子事务ID,当存在子事务时,从父事务开始计数,父事务为1,子事务按次序递增 - name
如果是子事务,保存子事务的名字,就是SAVEPOINT P1中的P1 - savepointlevel
子事务的嵌套深度, - nestinglevel
事务的嵌套深度,父事务为1,子事务依次递增 - gucNestLevel
GUC嵌套深度,因为每个子事务中可能会修改GUC参数,所以每个子事务都需要保存对应的GUC信息 - curTransactionContext
当前的事务上下文 - curTransactionOwner
当前事务的resourceOwner信息 - state
事务状态,主要的状态有
typedef enum TransState
{
TRANS_DEFAULT, /* 默认状态 */
TRANS_START, /* 启动事务 */
TRANS_INPROGRESS, /* 事务在运行中 */
TRANS_COMMIT, /* 事务提交 */
TRANS_ABORT, /* 事务终止 */
TRANS_PREPARE /* 两阶段提交事务执行 */
} TransState;
- blockState
事务块状态,状态流转情况前面已经描述,不再赘述
事务块基本函数分析
我们就依据上面的示例所涉及的事务相关函数,深入分析一下具体事务执行的流程
上面说过任何一个SQL语句执行都会在语句开始执行前先执行StartTranscationCommand函数,在SQL执行之后会执行CommitTransactionCommand
BEGIN
- StartTranscationCommand
事务块默认的状态是TBLOCK_DEFAULT,这里要开启一个事务,调用StartTransaction启动一个事务,并将事务块状态修改为TBLOCK_STARTED
StartTransactionCommand(void)
{
TransactionState s = CurrentTransactionState;
switch (s->blockState) //跟据当前事务块的状态,修改为不同的状态
{
case TBLOCK_DEFAULT:
StartTransaction();
s->blockState = TBLOCK_STARTED;
break;
- StartTransaction
启动一个事务,并进行相关的初始化工作,主要就是初始化事务结构体TransactionState,并将事务状态设置为TRANS_INPROGRESS。每个进程或者说事务块中默认存在一个事务结构体,存放在全局变量TopTransactionStateData中,只用将CurrentTransactionState指向这个事务,就将该事务切换为当前的事务了,就可以进行事务操作了。
/
启动事务
*/
static void
StartTransaction(void)
{
TransactionState s;
VirtualTransactionId vxid;
/*
* 获取当前的事务状态结构体
*/
s = &TopTransactionStateData;
CurrentTransactionState = s;
Assert(!FullTransactionIdIsValid(XactTopFullTransactionId)); //判断事务ID是否有效(是否为0)
/* 启动一个事务时,事务的状态必须是TRANS_DEFAULT状态 */
Assert(s->state == TRANS_DEFAULT);
s->state = TRANS_START; //事务状态设置为start
s->fullTransactionId = InvalidFullTransactionId; //事务ID设置未无效,后面有IUP操作时会再分配
/* Determine if statements are logged in this transaction, 判断事务日志是否需要记录 */
xact_is_sampled = log_xact_sample_rate != 0 &&
(log_xact_sample_rate == 1 ||
random() <= log_xact_sample_rate * MAX_RANDOM_VALUE);
/*
* initialize current transaction state fields
* 初始化事务结构体中子事务相关的值,这里不是子事务,所以初始化为初始值
*/
s->nestingLevel = 1;
s->gucNestLevel = 1;
s->childXids = NULL;
s->nChildXids = 0;
s->maxChildXids = 0;
GetUserIdAndSecContext(&s->prevUser, &s->prevSecContext); //设置当前事务的用户及安全上下文标志
/* SecurityRestrictionContext should never be set outside a transaction */
Assert(s->prevSecContext == 0);
if (RecoveryInProgress()) //如果是在恢复中,设置对应变量,并且事务设置为只读,恢复模式有些事务操作不能执行。
{
s->startedInRecovery = true;
XactReadOnly = true;
}
else
{
s->startedInRecovery = false;
XactReadOnly = DefaultXactReadOnly;
}
XactDeferrable = DefaultXactDeferrable;
XactIsoLevel = DefaultXactIsoLevel;
forceSyncCommit = false;
MyXactFlags = 0;
/*
* reinitialize within-transaction counters
初始化事务ID计数,command计数等
*/
s->subTransactionId = TopSubTransactionId;
currentSubTransactionId = TopSubTransactionId;
currentCommandId = FirstCommandId;
currentCommandIdUsed = false;
/*
* initialize reported xid accounting
初始化xid报告计数,XLOG是否记录xid设置为false
*/
nUnreportedXids = 0;
s->didLogXid = false;
/*
* must initialize resource-management stuff first
初始化资源管理器
*/
AtStart_Memory(); //初始化内存上下文
AtStart_ResourceOwner(); //初始化资源管理器
/*
分配本地事务ID,这里的事务ID是指虚拟事务ID,而真正的事务ID分配会在执行IUD操作时才会分配
*/
vxid.backendId = MyBackendId; //进程ID
vxid.localTransactionId = GetNextLocalTransactionId(); //本地事务ID
/*
* Lock the virtual transaction id before we announce it in the proc array
虚拟事务ID加锁,并添加到进程中,后面可通过快速路径访问
*/
VirtualXactLockTableInsert(vxid);
Assert(MyProc->backendId == vxid.backendId);
MyProc->lxid = vxid.localTransactionId;//添加到proc中,这样就相当于广告了,可以被其他人访问
TRACE_POSTGRESQL_TRANSACTION_START(vxid.localTransactionId);
/*
在一个事务里,时间戳是事务开始时获取后就不会再变化的,但是对于并行worker,其父事务肯定已经被设置过了,这里就不需要再设置了
*/
if (!IsParallelWorker())
{
if (!SPI_inside_nonatomic_context())
xactStartTimestamp = stmtStartTimestamp;
else
xactStartTimestamp = GetCurrentTimestamp();
}
else
Assert(xactStartTimestamp != 0);
pgstat_report_xact_timestamp(xactStartTimestamp); //报告时间戳
/* Mark xactStopTimestamp as unset. */
xactStopTimestamp = 0;
/*
* initialize other subsystems for new transaction
初始化新事务的其他子系统,如GUC,cache,trigger
*/
AtStart_GUC(); //设置GUC嵌套深度为1,后面若是有子事务,嵌套深度会增加的
AtStart_Cache(); //初始化IM,消息一致性相关
AfterTriggerBeginXact(); //aftertrigger:事务块外的BEGIN或者implicit
/*
* done with start processing, set current transaction state to "in
* progress"
*/
s->state = TRANS_INPROGRESS; //事务状态修改为运行中
ShowTransactionState("StartTransaction");
- BeginTransactionBlock
执行SQL语句“BEGIN”,调用的是ProcessUtility->standard_ProcessUtility->BeginTransactionBlock,实际调用的是BeginTransactionBlock函数,该函数只修改了一下事务块的状态。 StartTransactionCommand函数已经将事务块状态设置为TBBLOCK_STARTED, 这里会将其设置为TBLOCK_BEGIN。
BeginTransactionBlock(void)
{
TransactionState s = CurrentTransactionState;
switch (s->blockState)
{
case TBLOCK_STARTED:
s->blockState = TBLOCK_BEGIN;
break;
- CommitTransactionCommand
因为BEGIN执行之后将事务块状态修改为了TBLOCK_BEGIN,这里则将事务块状态修改为TBLOCK_INPROCESS,表示一个事务从现在开始就在运行中了。
void
CommitTransactionCommand(void)
{
TransactionState s = CurrentTransactionState; //获取到当前事务
if (s->chain)//如果有新块,设置保存标记
SaveTransactionCharacteristics();
switch (s->blockState)
{
case TBLOCK_BEGIN:
s->blockState = TBLOCK_INPROGRESS;
break;
SELECT/INSERT
执行SELECT和INSERT的SQL时,事务块状态不会发生变化,都还是处于TBLOCK_INPROGRESS,在执行INSERT的操作时,会调用GetCurrentTransactionId为该事务分配一个全局事务ID
插入一条数据
*/
void
heap_insert(Relation relation, HeapTuple tup, CommandId cid,
int options, BulkInsertState bistate)
{
TransactionId xid = GetCurrentTransactionId(); //获取当前的事务ID,如果没有则分配一个新的事务ID
COMMIT
执行COMMIT的SQL时,会提交事务,并修改事务块状态
- StartTransactionCommand
没有修改事务块状态,还是处于TBLOCK_INPROGRESS状态
void
StartTransactionCommand(void)
{
TransactionState s = CurrentTransactionState;
switch (s->blockState) //跟据当前事务块的状态,修改喂不同的状态
{
case TBLOCK_INPROGRESS:
case TBLOCK_IMPLICIT_INPROGRESS:
case TBLOCK_SUBINPROGRESS:
break;
- EndTransactionBlock
执行COMMIT 的SQL命令时,实际调用的是ProcessUtility->standard_ProcessUtility->EndTransactionBlock, EndTransactionBlock函数也只会修改事务块的状态,不会做其他任何操作,这里将事务块状态修改为TBLOCK_END。
bool
EndTransactionBlock(bool chain)
{
TransactionState s = CurrentTransactionState;
bool result = false;
switch (s->blockState)
{
case TBLOCK_INPROGRESS:
s->blockState = TBLOCK_END;
result = true;
break;
- CommitTransactionCommand
因为现在的事务块状态是TBLOCK_END,在CommitTransactionCommand对应的逻辑如下,它会调用CommitTransaction函数提交事务,事务提交后会将事务块的状态重置为
TBLOCK_DEFAULT。
void
CommitTransactionCommand(void)
{
TransactionState s = CurrentTransactionState; //获取到当前事务
if (s->chain)//如果有新块,设置保存标记
SaveTransactionCharacteristics();
switch (s->blockState)
{
case TBLOCK_END:
CommitTransaction(); //提交事务
s->blockState = TBLOCK_DEFAULT; //提交后事务块状态重置为DEFAULT
if (s->chain) //如果存在新申请的块,也要保存
{
StartTransaction();
s->blockState = TBLOCK_INPROGRESS;
s->chain = false;
RestoreTransactionCharacteristics();
}
break;
- CommitTransaction
事务提交真正的执行函数。主要内容就是提交事务,然后清理期间事务申请的各种资源。
- 事务提交前操作
事务提交前,需要先触发一下延迟触发的触发器,关闭大对象,关闭中断等操作
for (;;) //触发所有延迟触发器
{
AfterTriggerFireDeferred();
if (!PreCommit_Portals(false))
break;
}
CallXactCallbacks(is_parallel_worker ? XACT_EVENT_PARALLEL_PRE_COMMIT
: XACT_EVENT_PRE_COMMIT); //并行模式的回调函数
if (IsInParallelMode()) //如果是并行模式,现在就处理并行的一些上下文信息
AtEOXact_Parallel(true);
AfterTriggerEndXact(true); //关闭所有的defferred-trigger
PreCommit_on_commit_actions(); //在真正commit之前需要执行的一些动作
smgrDoPendingSyncs(true, is_parallel_worker); //同步还没有被WAL记录的文件
AtEOXact_LargeObject(true); //关闭大对象
PreCommit_Notify(); //提交前如果有待处理的LISTEN的操作,提前加入到共享内存的监听队列中
if (!is_parallel_worker)
PreCommit_CheckForSerializationFailure(); //如果是非并行模式,且事务隔离级别是串行化,需要检查是否有危险的结构
HOLD_INTERRUPTS();//关中断
AtEOXact_RelationMap(true, is_parallel_worker); //更新relation map
- 事务提交
事务提交主要是调用RecordTransactionCommit函数进行 - 事务提交后
事务提交后,主要就是释放事务期间使用的资源,重置使用的一些全局变量或共享内存中记录的信息,最后将事务状态改为DEFAULT
*/
ProcArrayEndTransaction(MyProc, latestXid); //通知其他进程,当前事务已经结束,主要是清理ProCArray上记录的事务信息
//下面就是事务提交后的清理工作,即使这里出现错误,也已经来不及回滚了
CallXactCallbacks(is_parallel_worker ? XACT_EVENT_PARALLEL_COMMIT
: XACT_EVENT_COMMIT);
ResourceOwnerRelease(TopTransactionResourceOwner,
RESOURCE_RELEASE_BEFORE_LOCKS,
true, true);//释放共享缓冲区和RelCache
AtEOXact_Buffers(true); //释放共享缓冲区的buffer pin
AtEOXact_RelationCache(true); //清理relation cache
AtEOXact_Inval(true); //IM消息通知,其他进程此时就可以看待该事务做出的修改了
AtEOXact_MultiXact();//清理MultiXact相关的cache
ResourceOwnerRelease(TopTransactionResourceOwner,
RESOURCE_RELEASE_LOCKS,
true, true);//释放锁
ResourceOwnerRelease(TopTransactionResourceOwner,
RESOURCE_RELEASE_AFTER_LOCKS,
true, true);//释放其他资源
smgrDoPendingDeletes(true);//事务期间有drop操作时,删除对应的文件
AtCommit_Notify();//事务提交完成后且事务状态也已经更新到CLOG后,通知其他进程
AtEOXact_GUC(true, 1); //处理GUC配置更新
AtEOXact_SPI(true); //清理SPI状态
AtEOXact_Enum(); //重置uncommit tables
AtEOXact_on_commit_actions(true);//清理OnCommitItem信息
AtEOXact_Namespace(true, is_parallel_worker); //清理namespace
AtEOXact_SMgr();//关闭通过smgr打开的文件
AtEOXact_Files(true);//关闭打开的临时文件VFD
AtEOXact_ComboCid();//重置comboCid相关全局变量
AtEOXact_HashTables(true);//关闭所有打开的scan
AtEOXact_PgStat(true, is_parallel_worker); //pg_stat统计更新
AtEOXact_Snapshot(true, false);//清理相关的快照信息
AtEOXact_ApplyLauncher(true);//唤醒提交后登录
pgstat_report_xact_timestamp(0);//重置pgstat的事务开始时间记录
CurrentResourceOwner = NULL;
ResourceOwnerDelete(TopTransactionResourceOwner);//删除该事务的resourceOwner
s->curTransactionOwner = NULL;
CurTransactionResourceOwner = NULL;
TopTransactionResourceOwner = NULL;
AtCommit_Memory(); //重置事务相关的内存上下文
//重置事务状态结构体
s->fullTransactionId = InvalidFullTransactionId;
s->subTransactionId = InvalidSubTransactionId;
s->nestingLevel = 0;
s->gucNestLevel = 0;
s->childXids = NULL;
s->nChildXids = 0;
s->maxChildXids = 0;
XactTopFullTransactionId = InvalidFullTransactionId;
nParallelCurrentXids = 0;
s->state = TRANS_DEFAULT; //事务状态重置为DEFAULT
RESUME_INTERRUPTS(); //释放中断
- RecordTransactionCommit
该函数主要是提交事务时,记录相应的信息并写入WAL日志中。
如果事务执行过程中,没有IUD操作,也就没有分配事务ID,这就不会将事务记录到WAL日志中的。但是如果是主备模式,即使没有事务ID,那么就需要将IM消息记录写入到WAL日志中。
TransactionId xid = GetTopTransactionIdIfAny(); //获取到事务ID
bool markXidCommitted = TransactionIdIsValid(xid);
TransactionId latestXid = InvalidTransactionId;
int nrels;
RelFileNode *rels;
int nchildren;
TransactionId *children;
int nmsgs = 0;
SharedInvalidationMessage *invalMessages = NULL;
bool RelcacheInitFileInval = false;
bool wrote_xlog;
if (XLogLogicalInfoActive())
LogLogicalInvalidations(); //将invalidations记录到WAL日志中
/* Get data needed for commit record */
nrels = smgrGetPendingDeletes(true, &rels); //获取要删除的非临时文件列表
nchildren = xactGetCommittedChildren(&children);//获取当前事务的已经提交的子事务列表
if (XLogStandbyInfoActive())//如果主备模式,还需要将IM消息记录到事务提交记录中
nmsgs = xactGetCommittedInvalidationMessages(&invalMessages,
&RelcacheInitFileInval);
wrote_xlog = (XactLastRecEnd != 0); //是否写XLOG
if (!markXidCommitted)//如果没有分配事务ID,就不需要记录事务提交记录了
{
if (nrels != 0)
elog(ERROR, "cannot commit a transaction that deleted files but has no xid");
if (nmsgs != 0)
{
LogStandbyInvalidations(nmsgs, invalMessages,
RelcacheInitFileInval); //有要写入的IM消息,这时候还是要写XLOG的
wrote_xlog = true; /* not strictly necessary */
}
if (!wrote_xlog) //如果不需要写XLOG,直接调到执行清理的步骤即可
goto cleanup;
}
如果已经分配了事务ID,那么就需要将事务记录写入到WAL日志,事务提交信息写入到CommitTs日志中。
bool replorigin;
replorigin = (replorigin_session_origin != InvalidRepOriginId &&
replorigin_session_origin != DoNotReplicateId);
BufmgrCommit(); //啥也没干
START_CRIT_SECTION(); //开启临界区,也就是说下面的操作不允许有错误,出错就是CRITICAL
MyProc->delayChkpt = true; //推迟当前进程的checkpoint
SetCurrentTransactionStopTimestamp();//更新事务结束时间戳
//事务提交记录写XLOG日志
XactLogCommitRecord(xactStopTimestamp,
nchildren, children, nrels, rels,
nmsgs, invalMessages,
RelcacheInitFileInval,
MyXactFlags,
InvalidTransactionId, NULL /* plain commit */ );
if (replorigin) //如果配置了流复制功能,需要移动LSN
/* Move LSNs forward for this replication origin */
replorigin_session_advance(replorigin_session_origin_lsn,
XactLastRecEnd);
if (!replorigin || replorigin_session_origin_timestamp == 0)
replorigin_session_origin_timestamp = xactStopTimestamp;
//将事务提交结果写入到CommitTS中
TransactionTreeSetCommitTsData(xid, nchildren, children,
replorigin_session_origin_timestamp,
replorigin_session_origin);
如果是同步模式的话,就会直接将WAL日志刷入磁盘,并将结果写入到CLOG中
如果是异步模式的话,则记录最新的异步提交的LSN,等待WAL writer刷该事务的COMMIT信息。
离开临界区后,更新最新的事务的ID,然后清理相关信息后,返回最新的事务ID。
if ((wrote_xlog && markXidCommitted &&
synchronous_commit > SYNCHRONOUS_COMMIT_OFF) ||
forceSyncCommit || nrels > 0) //如果允许写XLOG,且同步提交关闭后,可以先刷XLOG到磁盘
{
XLogFlush(XactLastRecEnd); //将XLOG日志刷入到磁盘
if (markXidCommitted)
TransactionIdCommitTree(xid, nchildren, children); //将事务提交结果写入到CLOG中
}
else //异步提交
{
XLogSetAsyncXactLSN(XactLastRecEnd);//报告最新的异步提交LSN,这样WAL writer就知道刷新该commit信息了
if (markXidCommitted) //保存LSN直到CLOG被更新后
TransactionIdAsyncCommitTree(xid, nchildren, children, XactLastRecEnd);
}
if (markXidCommitted)
{
MyProc->delayChkpt = false; //取消进程checkpoint推迟
END_CRIT_SECTION(); //离开临界区
}
latestXid = TransactionIdLatest(xid, nchildren, children); //如果有子事务,计算出最新的事务ID
if (wrote_xlog && markXidCommitted)
SyncRepWaitForLSN(XactLastRecEnd, true);//如果有需求,等待同步复制
XactLastCommitEnd = XactLastRecEnd;
XactLastRecEnd = 0;
cleanup:
/* Clean up local data */
if (rels)
pfree(rels);//释放本地数据
其他函数
当事务出现错误或执行ROLLBACK时,会回滚事务,主要就是调用AbortTransaction函数
- AbortTransaction
重置事务执行期间申请的资源或配置的变量,事务状态修改为ABORT
TransactionState s = CurrentTransactionState;
TransactionId latestXid;
bool is_parallel_worker;
/* Prevent cancel/die interrupt while cleaning up */
HOLD_INTERRUPTS(); //屏蔽中断
/* Make sure we have a valid memory context and resource owner */
AtAbort_Memory(); //确认内存上下文是否可用
AtAbort_ResourceOwner(); //确认resourceOwner是否可用
LWLockReleaseAll(); //释放所有轻量锁
pgstat_report_wait_end(); //pgstat报告等待结束
pgstat_progress_end_command(); //pgstat通知进程结束
AbortBufferIO(); //清理所有的buffer I/O
UnlockBuffers();//释放所有buffer上的content 锁
XLogResetInsertion(); // 重置WAL 插入相关变量
ConditionVariableCancelSleep(); //取消条件变量睡眠
LockErrorCleanup(); //取消锁等待
reschedule_timeouts();//如果有超时时间仍然存活的话,执行超时中断
PG_SETMASK(&UnBlockSig); //重置全局变量
is_parallel_worker = (s->blockState == TBLOCK_PARALLEL_INPROGRESS);
if (s->state != TRANS_INPROGRESS && s->state != TRANS_PREPARE)
elog(WARNING, "AbortTransaction while in %s state",
TransStateAsString(s->state));
Assert(s->parent == NULL);
s->state = TRANS_ABORT; //事务状态修改为abort
SetUserIdAndSecContext(s->prevUser, s->prevSecContext); //重置上下文
ResetReindexState(s->nestingLevel); //重置索引
ResetLogicalStreamingState(); //重置逻辑复制
SnapBuildResetExportedSnapshotState();//重置快照
if (IsInParallelMode()) //并行模式下,清理works
{
AtEOXact_Parallel(false);
s->parallelModeLevel = 0;
}
AfterTriggerEndXact(false); /* 'false' means it's abort 取消所有还未触发的的触发器*/
AtAbort_Portals(); //处理portals
smgrDoPendingSyncs(false, is_parallel_worker);//处理文件同步,删除相应的文件
AtEOXact_LargeObject(false); //处理大文件对象
AtAbort_Notify();//处理notify信息
AtEOXact_RelationMap(false, is_parallel_worker);//处理relation map
AtAbort_Twophase();//解锁占用的全局事务结构变量
事务终止时调用的RecordTransactionAbort函数,该函数记录该事务终止信息,并写入到WAL日志中
if (!is_parallel_worker)
latestXid = RecordTransactionAbort(false); //事务退出
else
{
latestXid = InvalidTransactionId;
XLogSetAsyncXactLSN(XactLastRecEnd); //并行模式,我们只更新LSN即可
}
之后就是一些常规的清理了,这里如果resourceOwner没有创建成功就失败了,那么下面许多清理就不需要再做了
ProcArrayEndTransaction(MyProc, latestXid);//从ProcArray中删除当前事务相关的信息
if (TopTransactionResourceOwner != NULL) //如果是resourceOwner就没创建就事务终止了,下面的清理就不需要做了
{
if (is_parallel_worker)
CallXactCallbacks(XACT_EVENT_PARALLEL_ABORT);
else
CallXactCallbacks(XACT_EVENT_ABORT);
ResourceOwnerRelease(TopTransactionResourceOwner,
RESOURCE_RELEASE_BEFORE_LOCKS,
false, true);
AtEOXact_Buffers(false);
AtEOXact_RelationCache(false);
AtEOXact_Inval(false);
AtEOXact_MultiXact();
ResourceOwnerRelease(TopTransactionResourceOwner,
RESOURCE_RELEASE_LOCKS,
false, true);
ResourceOwnerRelease(TopTransactionResourceOwner,
RESOURCE_RELEASE_AFTER_LOCKS,
false, true);
smgrDoPendingDeletes(false);
AtEOXact_GUC(false, 1);
AtEOXact_SPI(false);
AtEOXact_Enum();
AtEOXact_on_commit_actions(false);
AtEOXact_Namespace(false, is_parallel_worker);
AtEOXact_SMgr();
AtEOXact_Files(false);
AtEOXact_ComboCid();
AtEOXact_HashTables(false);
AtEOXact_PgStat(false, is_parallel_worker);
AtEOXact_ApplyLauncher(false);
pgstat_report_xact_timestamp(0);
}
RESUME_INTERRUPTS(); //释放中断
- RecordTransactionAbort
跟recordTransactionCommit差不多流程,就不多赘述了,主要就是记录事务结束日志到WAL日志中。
TransactionId xid = GetCurrentTransactionIdIfAny();//获取当前的事务ID
TransactionId latestXid;
int nrels;
RelFileNode *rels;
int nchildren;
TransactionId *children;
TimestampTz xact_time;
if (!TransactionIdIsValid(xid)) //如果事务ID没有分配,直接返回即可
{
/* Reset XactLastRecEnd until the next transaction writes something */
if (!isSubXact)
XactLastRecEnd = 0;
return InvalidTransactionId;
}
if (TransactionIdDidCommit(xid)) //如果事务已经提交了,则不能再abort了,即过了RecordTransactionCommit阶段就不能abort或回滚了
elog(PANIC, "cannot abort transaction %u, it was already committed",
xid);
nrels = smgrGetPendingDeletes(false, &rels); //获取记录的数据
nchildren = xactGetCommittedChildren(&children);
START_CRIT_SECTION();
if (isSubXact)
xact_time = GetCurrentTimestamp();
else
{
SetCurrentTransactionStopTimestamp();
xact_time = xactStopTimestamp;
}
XactLogAbortRecord(xact_time,
nchildren, children,
nrels, rels,
MyXactFlags, InvalidTransactionId,
NULL);//记录abort的记录数据,两阶段提交可能会用
if (!isSubXact)
XLogSetAsyncXactLSN(XactLastRecEnd); //获取最新的LSN
TransactionIdAbortTree(xid, nchildren, children);//CLOG中更新事务状态
END_CRIT_SECTION(); //关闭临界区
/* Compute latestXid while we have the child XIDs handy */
latestXid = TransactionIdLatest(xid, nchildren, children);//获取最新的事务ID,主要是判断当前的事务ID和子事务ID的大小,取最大值
if (isSubXact)
XidCacheRemoveRunningXids(xid, nchildren, children, latestXid);//从proc中清除失败的事务ID
if (!isSubXact)
XactLastRecEnd = 0;
if (rels)
pfree(rels);
return latestXid;
【参考】
- 《PostgreSQL数据库内核分析》
- 《Postgresql技术内幕-事务处理深度探索》
- pg14源码