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


    相关

    《Postgresql源码(23)Clog使用的Slru页面淘汰机制》

    《Postgresql源码(27)为什么事务提交会通过delayChkpt阻塞checkpoint》

    《Postgresql源码(59)事务ID取值和判断规律总结》

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

    • PG中的事务处理按提供的功能可以分为两大部分:基本事务状态管理、子事务状态管理。

    • PG的事务系统总结起来一句话:用户命令触发状态机函数导致事务状态流转,流转时按对应状态调用底层事务处理函数干活。

    1 状态机流转系统

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

    状态机流转函数

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

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

    底层事务处理函数

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

    • 基础事务功能
    // 启动事务时调用,配置事务状态,申请资源等
    StartTransaction
    // 事务正常提交是调用
    CommitTransaction
    // 事务回滚时调用,先调AbortTransaction,在调CleanupTransaction
    CleanupTransaction
    // 事务回滚时调用
    AbortTransaction
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 子事务功能
    StartSubTransaction
    CommitSubTransaction
    AbortSubTransaction
    CleanupSubTransaction
    
    • 1
    • 2
    • 3
    • 4
    内层事务处理函数
    外层状态机流转函数
    StartTransaction
    CommitTransaction
    CleanupTransaction
    AbortTransaction
    StartTransactionCommand
    CommitTransactionCommand

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

    StartTransaction

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

    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
    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    CommitTransaction(事务内无写操作)

    未分配事务ID

    1. 事务状态流转TRANS_COMMIT
    2. 清理
    3. 事务状态流转TRANS_DEFAULT
    CommitTransaction
      s->state = TRANS_COMMIT
      RecordTransactionCommit // do nothing
      // 清理
      s->state = TRANS_DEFAULT
    
    • 1
    • 2
    • 3
    • 4
    • 5

    3 事务ID分配

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

    1. 拿一个新的xid
    2. 配置到MyProc->xid
    3. 配置到ProcGlobal->xids[MyProc->pgxactoff]
    4. xid加锁XactLockTableInsert
    TransactionId
    GetCurrentTransactionId(void)
    {
    	TransactionState s = CurrentTransactionState;
    
    	if (!FullTransactionIdIsValid(s->fullTransactionId))
    		AssignTransactionId(s);
    	return XidFromFullTransactionId(s->fullTransactionId);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

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

    AssignTransactionId
      ...
      GetNewTransactionId
        ...
        MyProc->xid = xid;
        ProcGlobal->xids[MyProc->pgxactoff] = xid;
        ...
      XactLockTableInsert
      ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    4 子事务系统

    例子:

    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);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    下面记录一些和普通事务的差异点:

    事务状态:子事务会使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}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    在分配事务ID时,每个子事务都会拿到自己的XID。

    AssignTransactionId
          ...
      		SubTransSetParent(XidFromFullTransactionId(s->fullTransactionId),
    						  XidFromFullTransactionId(s->parent->fullTransactionId));
    
    
    • 1
    • 2
    • 3
    • 4
    • 5

    并把自己上一层的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变成提交状态(类似于两阶段提交)
    CommitTransaction
      RecordTransactionCommit
        TransactionIdCommitTree
          TransactionIdSetTreeStatus
            // 一个CLOG页面全部搞定
            TransactionIdSetPageStatus
              TransactionIdSetPageStatusInternal
                // 每一个subxid都配置TRANSACTION_STATUS_SUB_COMMITTED
                for
                  TransactionIdSetStatusBit
                // 配置主事务的
                TransactionIdSetStatusBit
                // 再配置子事务的提交
                for
                  TransactionIdSetStatusBit
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
  • 相关阅读:
    k8s 中 Pod 的控制器
    千访 | “霸总”人设揽粉近十万!小红书企业号还能这么玩?
    CLIP与DINOv2的图像相似度对比
    BI技巧丨利用Rank函数排名
    卸载Docker
    gitea的简单介绍
    移动支付新时代——低代码如何对接支付宝和微信支付
    2_5.Linux存储的基本管理
    掌握Node Version Manager(nvm):跨平台Node.js版本管理
    uniapp 下拉框数据回显的问题
  • 原文地址:https://blog.csdn.net/jackgo73/article/details/125627855