前篇我们说到,执行dml操作时,才会为事务分配事务id。不过,即使没有事务id,事务也会用一个虚拟事务id来代表自己。
虚拟事务id由两部分组成:backendId(后台进程id,会话独有)+ localTransactionId(进程维护的本地事务id),以下结构体代码在 lock.h
typedef struct
{
BackendId backendId; /* backendId from PGPROC */
LocalTransactionId localTransactionId; /* lxid from PGPROC */
} VirtualTransactionId;
在事务启动阶段,由于不知道事务是否会有dml操作,此时只会先分配虚拟事务id。
static void
StartTransaction(void)
{
TransactionState s; //事务栈结构体
VirtualTransactionId vxid; //虚拟事务id
/*
* 确保事务栈是空的
*/
s = &TopTransactionStateData;
CurrentTransactionState = s; //当前事务
Assert(!FullTransactionIdIsValid(XactTopFullTransactionId)); //未分配事务id?
/* check the current transaction state */
Assert(s->state == TRANS_DEFAULT);
/*
* Set the current transaction state information appropriately during
* start processing. Note that once the transaction status is switched
* this process cannot fail until the user ID and the security context
* flags are fetched below.
* 在启动过程中设置当前事务状态信息。请注意,一旦切换了事务状态,在后续获取用户ID和安全上下文标志前,不会出现异常。
*/
s->state = TRANS_START; //修改事务状态
s->fullTransactionId = InvalidFullTransactionId; /* until assigned,因为事务id尚未分配,目前是invalid的,InvalidFullTransactionId其实就是0 */
/* 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
* note: prevXactReadOnly is not used at the outermost level
* 初始化事务结构体字段,注意:prevXactReadOnly不会在最外层中使用
*/
s->nestingLevel = 1;
s->gucNestLevel = 1;
s->childXids = NULL;
s->nChildXids = 0;
s->maxChildXids = 0;
/*
* Once the current user ID and the security context flags are fetched,
* both will be properly reset even if transaction startup fails.
* 一旦当前用户ID和安全上下文标记已提取,即使事务启动失败,也会正确地重置它们。
*/
GetUserIdAndSecContext(&s->prevUser, &s->prevSecContext);
/* SecurityRestrictionContext should never be set outside a transaction,SecurityRestrictionContext不应在事务外设置 */
Assert(s->prevSecContext == 0);
/*
* Make sure we've reset xact state variables
*
* If recovery is still in progress, mark this transaction as read-only.
* We have lower level defences in XLogInsert and elsewhere to stop us
* from modifying data during recovery, but this gives the normal
* indication to the user that the transaction is read-only.
* 如仍处于恢复过程,标志此事务为只读
* 在XLogInsert中和其他地方有低级别的保护机制确保在恢复过程中不会更新数据,只是给用户正常的提示,说明事务只读
*/
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,重新初始化事务内计数器
*/
s->subTransactionId = TopSubTransactionId;
currentSubTransactionId = TopSubTransactionId;
currentCommandId = FirstCommandId;
currentCommandIdUsed = false;
/*
* initialize reported xid accounting,初始化已报告的事务计数
*/
nUnreportedXids = 0;
s->didLogXid = false;
/*
* must initialize resource-management stuff first,首先初始化资源管理器
*/
AtStart_Memory(); //初始化两个内存上下文,一个用于abort事务、一个用于top事务
AtStart_ResourceOwner();//初始化资源跟踪器,其中包含buffer信息、锁信息等
/*
* 前面提到的,虚拟事务id的两个组成部分
*/
vxid.backendId = MyBackendId;
vxid.localTransactionId = GetNextLocalTransactionId();
/*
* Lock the virtual transaction id before we announce it in the proc array。锁住该虚拟事务id
*/
VirtualXactLockTableInsert(vxid);
/*
* Advertise it in the proc array. We assume assignment of
* localTransactionId is atomic, and the backendId should be set already.
* 在proc array中声明。假定LocalTransactionID是原子的且backendId已分配。将本地事务id保存至当前进程队列(PROC)
*/
Assert(MyProc->backendId == vxid.backendId);
MyProc->lxid = vxid.localTransactionId;
TRACE_POSTGRESQL_TRANSACTION_START(vxid.localTransactionId);
/*
* 设置时间戳。设置事务开始时间=命令开始时间,并初始化事务结束时间=0
*/
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
*/
AtStart_GUC(); //多个子事务可能设置不同的GUC参数,需要记录修改历史,在rollback子事务时进行恢复
AtStart_Cache(); //失效消息机制
AfterTriggerBeginXact();// after-trigger相关
补充:GUC参数案例如下
/*
* done with start processing, set current transaction state to "in
* progress"。启动事务完成,将事务状态改为TRANS_INPROGRESS
*/
s->state = TRANS_INPROGRESS;
ShowTransactionState("StartTransaction");
}
至此执行完毕,输入c结束,begin命令运行完成
3. 执行流程概要
参考
《PostgreSQL技术内幕:事务处理深度探索》第1章
《PostgreSQL数据库内核分析》第7章
http://blog.itpub.net/6906/viewspace-2564019/