相关 《Postgresql源码(23)Clog使用的Slru页面淘汰机制》 《Postgresql源码(27)为什么事务提交会通过delayChkpt阻塞checkpoint》 《Postgresql源码(59)事务ID取值和判断规律总结》
重新总结下PG的事务管理系统:
用户命令触发状态机函数导致事务状态流转,流转时按对应状态调用底层事务处理函数干活。
12个状态机流转函数,可以分成三类。
b StartTransactionCommand
b CommitTransactionCommand
// 系统内部调用回滚
b AbortCurrentTransaction
// 用户执行begin
b BeginTransactionBlock
// 用户执行commit
b EndTransactionBlock
// 用户执行rollback、abort
b UserAbortTransactionBlock
b DefineSavepoint
b ReleaseSavepoint
b RollbackToSavepoint
b BeginInternalSubTransaction
b RollbackAndReleaseCurrentSubTransaction
b AbortOutOfAnyTransaction
状态机流转时会调用底层函数干活,函数分两类
// 启动事务时调用,配置事务状态,申请资源等
StartTransaction
// 事务正常提交是调用
CommitTransaction
// 事务回滚时调用,先调AbortTransaction,在调CleanupTransaction
CleanupTransaction
// 事务回滚时调用
AbortTransaction
StartSubTransaction
CommitSubTransaction
AbortSubTransaction
CleanupSubTransaction
#mermaid-svg-NGahSiAtk5NxDxOA {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-NGahSiAtk5NxDxOA .error-icon{fill:#552222;}#mermaid-svg-NGahSiAtk5NxDxOA .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-NGahSiAtk5NxDxOA .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-NGahSiAtk5NxDxOA .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-NGahSiAtk5NxDxOA .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-NGahSiAtk5NxDxOA .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-NGahSiAtk5NxDxOA .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-NGahSiAtk5NxDxOA .marker{fill:#333333;stroke:#333333;}#mermaid-svg-NGahSiAtk5NxDxOA .marker.cross{stroke:#333333;}#mermaid-svg-NGahSiAtk5NxDxOA svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-NGahSiAtk5NxDxOA .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-NGahSiAtk5NxDxOA .cluster-label text{fill:#333;}#mermaid-svg-NGahSiAtk5NxDxOA .cluster-label span{color:#333;}#mermaid-svg-NGahSiAtk5NxDxOA .label text,#mermaid-svg-NGahSiAtk5NxDxOA span{fill:#333;color:#333;}#mermaid-svg-NGahSiAtk5NxDxOA .node rect,#mermaid-svg-NGahSiAtk5NxDxOA .node circle,#mermaid-svg-NGahSiAtk5NxDxOA .node ellipse,#mermaid-svg-NGahSiAtk5NxDxOA .node polygon,#mermaid-svg-NGahSiAtk5NxDxOA .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-NGahSiAtk5NxDxOA .node .label{text-align:center;}#mermaid-svg-NGahSiAtk5NxDxOA .node.clickable{cursor:pointer;}#mermaid-svg-NGahSiAtk5NxDxOA .arrowheadPath{fill:#333333;}#mermaid-svg-NGahSiAtk5NxDxOA .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-NGahSiAtk5NxDxOA .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-NGahSiAtk5NxDxOA .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-NGahSiAtk5NxDxOA .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-NGahSiAtk5NxDxOA .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-NGahSiAtk5NxDxOA .cluster text{fill:#333;}#mermaid-svg-NGahSiAtk5NxDxOA .cluster span{color:#333;}#mermaid-svg-NGahSiAtk5NxDxOA div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-NGahSiAtk5NxDxOA :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}
内层事务处理函数
外层状态机流转函数
StartTransaction
CommitTransaction
CleanupTransaction
AbortTransaction
StartTransactionCommand
CommitTransactionCommand
StartTransaction
// vxid = {backendId = 3, localTransactionId = 76407}
GetNextLocalTransactionId
VirtualXactLockTableInsert
...
s->state = TRANS_INPROGRESS;
...
已分配事务ID的场景
CommitTransaction
s->state = TRANS_COMMIT
RecordTransactionCommit
[START_CRIT_SECTION]
[[MyProc->delayChkpt = true]]
XactLogCommitRecord
XLogFlush
TransactionIdCommitTree
[[MyProc->delayChkpt = false]]
[END_CRIT_SECTION]
ProcArrayEndTransaction
// 能拿到锁正常清理,拿不到锁加到list里面等着后面清理
LWLockConditionalAcquire(ProcArrayLock, LW_EXCLUSIVE)
// 正常清理:清理MyProc和ProcGlobal里面记录的xid信息
ProcArrayEndTransactionInternal
...
// 清理
...
s->state = TRANS_DEFAULT
未分配事务ID
CommitTransaction
s->state = TRANS_COMMIT
RecordTransactionCommit // do nothing
// 清理
s->state = TRANS_DEFAULT
在真正要写数据前,会调用GetCurrentTransactionId,比如heap_insert。
TransactionId
GetCurrentTransactionId(void)
{
TransactionState s = CurrentTransactionState;
if (!FullTransactionIdIsValid(s->fullTransactionId))
AssignTransactionId(s);
return XidFromFullTransactionId(s->fullTransactionId);
}
如果没分配过,执行AssignTransactionId拿一个新的xid分配给TransactionState。参考这一篇(《Postgresql源码(59)事务ID取值和判断规律总结》)
AssignTransactionId
...
GetNewTransactionId
...
MyProc->xid = xid;
ProcGlobal->xids[MyProc->pgxactoff] = xid;
...
XactLockTableInsert
...
例子:
drop table t1;
create table t1 (c1 int, c2 int);
begin;
insert into t1 values (1,1);
savepoint a;
insert into t1 values (2,1);
savepoint b;
insert into t1 values (3,1);
下面记录一些和普通事务的差异点:
事务状态:子事务会使CurrentTransactionState有多层结构,之间使用parent连接。
p *CurrentTransactionState
$39 = {fullTransactionId = {value = 4000071}, subTransactionId = 3, name = 0x199ca60 "b", savepointLevel = 0,
state = TRANS_INPROGRESS, blockState = TBLOCK_SUBINPROGRESS, nestingLevel = 3, gucNestLevel = 3,
curTransactionContext = 0x19de090, curTransactionOwner = 0x192a458, childXids = 0x0, nChildXids = 0, maxChildXids = 0,
prevUser = 10, prevSecContext = 0, prevXactReadOnly = false, startedInRecovery = false, didLogXid = false,
parallelModeLevel = 0, chain = false, assigned = false, parent = 0x199c558}
p *CurrentTransactionState->parent
$40 = {fullTransactionId = {value = 4000070}, subTransactionId = 2, name = 0x199c6c0 "a", savepointLevel = 0,
state = TRANS_INPROGRESS, blockState = TBLOCK_SUBINPROGRESS, nestingLevel = 2, gucNestLevel = 2,
curTransactionContext = 0x19d7200, curTransactionOwner = 0x19164c8, childXids = 0x0, nChildXids = 0, maxChildXids = 0,
prevUser = 10, prevSecContext = 0, prevXactReadOnly = false, startedInRecovery = false, didLogXid = true, parallelModeLevel = 0,
chain = false, assigned = false, parent = 0xe6a940 <TopTransactionStateData>}
p *CurrentTransactionState->parent->parent
$41 = {fullTransactionId = {value = 4000069}, subTransactionId = 1, name = 0x0, savepointLevel = 0, state = TRANS_INPROGRESS,
blockState = TBLOCK_INPROGRESS, nestingLevel = 1, gucNestLevel = 1, curTransactionContext = 0x199c400,
curTransactionOwner = 0x191e3e8, childXids = 0x0, nChildXids = 0, maxChildXids = 0, prevUser = 10, prevSecContext = 0,
prevXactReadOnly = false, startedInRecovery = false, didLogXid = true, parallelModeLevel = 0, chain = false, assigned = false,
parent = 0x0}
在分配事务ID时,每个子事务都会拿到自己的XID。
AssignTransactionId
...
SubTransSetParent(XidFromFullTransactionId(s->fullTransactionId),
XidFromFullTransactionId(s->parent->fullTransactionId));
并把自己上一层的xid记录到subtrans中(SLRU页面和CLOG使用的是相同的机制,这篇讲了一部分《Postgresql源码(23)Clog使用的Slru页面淘汰机制》)。
子事务提交
CommitTransaction
RecordTransactionCommit
TransactionIdCommitTree
TransactionIdSetTreeStatus
// 一个CLOG页面全部搞定
TransactionIdSetPageStatus
TransactionIdSetPageStatusInternal
// 每一个subxid都配置TRANSACTION_STATUS_SUB_COMMITTED
for
TransactionIdSetStatusBit
// 配置主事务的
TransactionIdSetStatusBit
// 再配置子事务的提交
for
TransactionIdSetStatusBit