赞
踩
注意区分pg中事务块和事务的概念
因此这里说的事务状态是指,底层事务(事务块中sql语句)真正的状态
- 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;
由于子事务的引入,一个事务中可能会有多个层级的子事务。pg使用一个事务栈来保存每个层级子事务的状态,这个事务栈的结构体是 TransactionStateData (其实上一篇它就出现了)
- typedef struct TransactionStateData
- {
- FullTransactionId fullTransactionId; /* 事务Id */
- SubTransactionId subTransactionId; /* 子事务ID */
- char *name; /* savepoint名字 */
- int savepointLevel; /* savepoint层级,因为可以有多层子事务 */
- TransState state; /* 事务状态 */
- TBlockState blockState; /* 事务块状态 */
- int nestingLevel; /* 事务嵌套深度 */
- int gucNestLevel; /* GUC(Grand Unified Configuration,全局统一配置) 上下文嵌套深度,与子事务出入栈相关 */
- MemoryContext curTransactionContext; /* 事务当前上下文 */
- ResourceOwner curTransactionOwner; /* 当前事务占有的资源 */
- TransactionId *childXids; /* 提交的子事务链表 */
- int nChildXids; /* 提交的子事务个数 */
- int maxChildXids; /* 已分配的子事务 childXids[] 存储空间 */
- Oid prevUser; /* 记录前一个 CurrentUserId(用户名) 设置 */
- int prevSecContext; /* previous SecurityRestrictionContext */
- bool prevXactReadOnly; /* 只读事务 */
- bool startedInRecovery; /* did we start in recovery? */
- bool didLogXid; /* has xid been included in WAL record? */
- int parallelModeLevel; /* Enter/ExitParallelMode counter */
- bool chain; /* 是否执行了 commit and chain */
- bool assigned; /* assigned to top-level XID */
- struct TransactionStateData *parent; /* 指向上层事务的指针 */
- } TransactionStateData;
-
- typedef TransactionStateData *TransactionState;
select txid_current_if_assigned();
我们构造一个包含子事务和dml操作的小案例
会话1
Create table t1(a int);
Savepoint p1;
Savepoint p2;
会话2
b GetNewTransactionId
b GetNewTransactionId
会话1
Insert into t1 values(1);
进入会话2进行调试
set print pretty on 格式化显示
可以看到这是最底层事务,savepoint name=p2
上层事务
顶层事务,name为空,parent也指向空
可以看到,最先进入AssignTransactionId()函数的参数是最底层事务(这里我们按照savepoint名字叫它p2),下面逐步来看。
- static void
- AssignTransactionId(TransactionState s)
- {
- bool isSubXact = (s->parent != NULL);
- …
- if (isSubXact && !FullTransactionIdIsValid(s->parent->fullTransactionId))
- {
- TransactionState p = s->parent;
- TransactionState *parents;
- size_t parentOffset = 0;
-
- parents = palloc(sizeof(TransactionState) * s->nestingLevel);
- while (p != NULL && !FullTransactionIdIsValid(p->fullTransactionId))
- {
- parents[parentOffset++] = p;
- p = p->parent;
- }
-
- /*
- * This is technically a recursive call, but the recursion will never
- * be more than one layer deep.
- */
- while (parentOffset != 0)
- AssignTransactionId(parents[--parentOffset]);
-
- pfree(parents);
- }
- …
- s->fullTransactionId = GetNewTransactionId(isSubXact);
这里我们只截取一些重要的调试过程
- if (isSubXact && !FullTransactionIdIsValid(s->parent->fullTransactionId))
- TransactionState p = s->parent;
- TransactionState *parents;
- size_t parentOffset = 0;
-
- parents = palloc(sizeof(TransactionState) * s->nestingLevel);
如果是子事务(函数最开始部分对isSubXact的定义是 isSubXact = (s->parent != NULL);),并且其父事务未分配事务id:做一些数据初始化,其中比较重要的是将p指向s的父事务,并根据s->nestingLevel(子事务深度)构造parents数组。
继续走到第一个while循环
- while (p != NULL && !FullTransactionIdIsValid(p->fullTransactionId))
- {
- parents[parentOffset++] = p;
- p = p->parent;
- }
如果没到顶层事务,并且当前事务未分配事务id:按照子事务->父事务的顺序填充该数组,填充结果如下:
继续走到第二个while循环
- while (parentOffset != 0)
- AssignTransactionId(parents[--parentOffset]);
-
- pfree(parents);
按照父事务->子事务的顺序递归调用AssignTransactionId()函数。通过这种方式,保证了父事务id一定先于子事务id分配(父事务id一定比子事务id小)。各层都递归执行完后,通过pfree释放parents数组占用资源。
开始递归执行,从顶层事务开始
注意这次它不符合 if (isSubXact && !FullTransactionIdIsValid(s->parent->fullTransactionId))
它是顶层的父事务,因此跳过了while循环,直接到了s->fullTransactionId = GetNewTransactionId(isSubXact); 分配实际的事务id
有时按函数名搜索源文件会遇到比较尴尬的情况——出来太多,不知道在哪里找
一个办法是通过gdb打断点
首先可以看到Xid来源
full_xid = ShmemVariableCache->nextXid;
xid = XidFromFullTransactionId(full_xid);
查看ShmemVariableCache,可以看到这是个VariableCache类型的指针
VariableCache的定义在VariableCacheData结构体(以后我们会详细研究它),其中包括事务id的计数器,每次获取事务id后会对计数器+1。
参考 PostgreSQL 源码解读(117)- MVCC#2(获取快照#2)_cuichao1900的博客-CSDN博客
(pg 14源码中没搜到)
继续往下,这个函数主要的部分在
- if (!isSubXact) //如果非子事务,需要记录到当前会话(PGROC)的事务结构体中
- {
- …
- /* LWLockRelease acts as barrier */
- // pg 14将事务id保存在PGROC,和ProcGlobal的镜像数组中
- MyProc->xid = xid;
- ProcGlobal->xids[MyProc->pgxactoff] = xid; //
- }
- else //如果是子事务,需要记录到ProcGlobal的子事务id数组中
- {
- XidCacheStatus *substat = &ProcGlobal->subxidStates[MyProc->pgxactoff];
- int nxids = MyProc->subxidStatus.count;
-
- Assert(substat->count == MyProc->subxidStatus.count);
- Assert(substat->overflowed == MyProc->subxidStatus.overflowed);
-
- if (nxids < PGPROC_MAX_CACHED_SUBXIDS) //子事务id数组最大长度
- {
- MyProc->subxids.xids[nxids] = xid;
- pg_write_barrier();
- MyProc->subxidStatus.count = substat->count = nxids + 1;
- }
- else //如果超出最大长度,要标记overflowed
- MyProc->subxidStatus.overflowed = substat->overflowed = true;
- }
这步过后,顶层事务就获取到了自己的事务id,通过递归调用AssignTransactionId(),依次给各子事务分配事务id。分配后结果如下:
分配事务id后
在GetNewTransactionId()后面是将事务ID的父子关系记入pg_subtrans目录
可以看到,如果是子事务,会为其记录父事务的事务id(单向记录),方便追溯
有一个疑惑:为什么TransactionState结构体中已经有记录了,这里还要记一下?是效率比较高?待研究。
参考:
https://blog.csdn.net/asmartkiller/article/details/121490543
《PostgreSQL技术内幕:事务处理深度探索》第1章
《PostgreSQL数据库内核分析》第7章
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。