前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Postgresql源码(60)事务系统框架总结

Postgresql源码(60)事务系统框架总结

作者头像
mingjie
发布2022-07-14 13:50:31
4860
发布2022-07-14 13:50:31
举报

相关 《Postgresql源码(23)Clog使用的Slru页面淘汰机制》 《Postgresql源码(27)为什么事务提交会通过delayChkpt阻塞checkpoint》 《Postgresql源码(59)事务ID取值和判断规律总结》

重新总结下PG的事务管理系统:

  • PG中的事务处理按提供的功能可以分为两大部分:基本事务状态管理、子事务状态管理。
  • PG的事务系统总结起来一句话:用户命令触发状态机函数导致事务状态流转,流转时按对应状态调用底层事务处理函数干活。

1 状态机流转系统

用户命令触发状态机函数导致事务状态流转,流转时按对应状态调用底层事务处理函数干活。

状态机流转函数

12个状态机流转函数,可以分成三类。

  • 包裹所有单行SQL的两个函数(进入SQL前StartTransactionCommand、SQL执行后CommitTransactionCommand),无论你执行的是begin、还是select 1,都会走一遍这两个函数。相当于事务状态的被动流转。
代码语言:javascript
复制
b StartTransactionCommand
b CommitTransactionCommand
  • 事务块处理函数,对应用户事务命令,在PortalRun里面调用,主动流转事务状态。
代码语言:javascript
复制
// 系统内部调用回滚
b AbortCurrentTransaction
// 用户执行begin
b BeginTransactionBlock
// 用户执行commit
b EndTransactionBlock
// 用户执行rollback、abort
b UserAbortTransactionBlock
  • 子事务状态流转。
代码语言:javascript
复制
b DefineSavepoint
b ReleaseSavepoint
b RollbackToSavepoint
b BeginInternalSubTransaction
b RollbackAndReleaseCurrentSubTransaction
b AbortOutOfAnyTransaction

底层事务处理函数

状态机流转时会调用底层函数干活,函数分两类

  • 基础事务功能
代码语言:javascript
复制
// 启动事务时调用,配置事务状态,申请资源等
StartTransaction
// 事务正常提交是调用
CommitTransaction
// 事务回滚时调用,先调AbortTransaction,在调CleanupTransaction
CleanupTransaction
// 事务回滚时调用
AbortTransaction
  • 子事务功能
代码语言:javascript
复制
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;}

代码语言:txt
复制
       内层事务处理函数          
代码语言:txt
复制
       外层状态机流转函数          
代码语言:txt
复制
       StartTransaction          
代码语言:txt
复制
       CommitTransaction          
代码语言:txt
复制
       CleanupTransaction          
代码语言:txt
复制
       AbortTransaction          
代码语言:txt
复制
       StartTransactionCommand          
代码语言:txt
复制
       CommitTransactionCommand          

2 底层事务处理函数做了什么?

StartTransaction

  1. 拿到vxid(由backendid和localid组合值)表示虚拟的事务id(写还未发生,不能分配真实xid)
  2. 用vxid注册MyProc,注册后在锁表中可以查询到vxid锁,表示事务启动了
  3. 事务状态流转到TRANS_INPROGRESS
代码语言:javascript
复制
StartTransaction
  // vxid = {backendId = 3, localTransactionId = 76407}
  GetNextLocalTransactionId
  VirtualXactLockTableInsert
  ...
  s->state = TRANS_INPROGRESS;
  ...

CommitTransaction(事务内有写操作)

已分配事务ID的场景

  1. 事务状态流转TRANS_COMMIT
  2. 开启对于checkpoint的临界区:MyProc->delayChkpt = true(《Postgresql源码(27)为什么事务提交会通过delayChkpt阻塞checkpoint》
  3. 写commit的事务日志、刷事务日志
  4. 写clog(不刷)TransactionIdCommitTree
  5. 清理ProcArray中的xid信息
  6. 清理其他
  7. 事务状态流转TRANS_DEFAULT
代码语言:javascript
复制
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

CommitTransaction(事务内无写操作)

未分配事务ID

  1. 事务状态流转TRANS_COMMIT
  2. 清理
  3. 事务状态流转TRANS_DEFAULT
代码语言:javascript
复制
CommitTransaction
  s->state = TRANS_COMMIT
  RecordTransactionCommit // do nothing
  // 清理
  s->state = TRANS_DEFAULT

3 事务ID分配

在真正要写数据前,会调用GetCurrentTransactionId,比如heap_insert。

  1. 拿一个新的xid
  2. 配置到MyProc->xid
  3. 配置到ProcGlobal->xidsMyProc->pgxactoff
  4. xid加锁XactLockTableInsert
代码语言:javascript
复制
TransactionId
GetCurrentTransactionId(void)
{
	TransactionState s = CurrentTransactionState;

	if (!FullTransactionIdIsValid(s->fullTransactionId))
		AssignTransactionId(s);
	return XidFromFullTransactionId(s->fullTransactionId);
}

如果没分配过,执行AssignTransactionId拿一个新的xid分配给TransactionState。参考这一篇(《Postgresql源码(59)事务ID取值和判断规律总结》

代码语言:javascript
复制
AssignTransactionId
  ...
  GetNewTransactionId
    ...
    MyProc->xid = xid;
    ProcGlobal->xids[MyProc->pgxactoff] = xid;
    ...
  XactLockTableInsert
  ...

4 子事务系统

例子:

代码语言:javascript
复制
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连接。

代码语言:javascript
复制
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。

代码语言:javascript
复制
AssignTransactionId
      ...
  		SubTransSetParent(XidFromFullTransactionId(s->fullTransactionId),
						  XidFromFullTransactionId(s->parent->fullTransactionId));

并把自己上一层的xid记录到subtrans中(SLRU页面和CLOG使用的是相同的机制,这篇讲了一部分《Postgresql源码(23)Clog使用的Slru页面淘汰机制》)。

子事务提交

  1. 除了上文(CommitTransaction(事务内有写操作))提到的步骤
  2. 在写CLOG时,和无子事务的情况有所区别
  3. 如果没有子事务,直接写CLOG即可
  4. 存在子事务时
    1. 在写CLOG时首先把子事务的XID配置SUB_COMMITTED状态到CLOG中
    2. 然后把父XID的提交状态配置进去
    3. 然后再把子事务的XID的状态从SUB_COMMITTED变成提交状态(类似于两阶段提交)
代码语言:javascript
复制
CommitTransaction
  RecordTransactionCommit
    TransactionIdCommitTree
      TransactionIdSetTreeStatus
        // 一个CLOG页面全部搞定
        TransactionIdSetPageStatus
          TransactionIdSetPageStatusInternal
            // 每一个subxid都配置TRANSACTION_STATUS_SUB_COMMITTED
            for
              TransactionIdSetStatusBit
            // 配置主事务的
            TransactionIdSetStatusBit
            // 再配置子事务的提交
            for
              TransactionIdSetStatusBit
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-07-05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1 状态机流转系统
    • 状态机流转函数
      • 底层事务处理函数
      • 2 底层事务处理函数做了什么?
        • StartTransaction
          • CommitTransaction(事务内有写操作)
            • CommitTransaction(事务内无写操作)
            • 3 事务ID分配
            • 4 子事务系统
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档