Postgresql源码(71)子事务数据结构与DDL

2022-11-30 16:11:38 浏览数 (1)

相关 子事务的可见性判断、性能问题请看这篇:《Postgresql源码(25)子事务可见性判断和性能问题》 子事务的DDL和数据结构请看这篇:《Postgresql源码(71)子事务数据结构与DDL分析》

1 SAVEPOINT、ROLLBACK执行细节

测试用例

代码语言:javascript复制
drop table table1;
create table table1(i int);

BEGIN;
    INSERT INTO table1 VALUES (1);
    -- s1
    SAVEPOINT my_savepoint1;
    INSERT INTO table1 VALUES (2);
    
    -- s2
    SAVEPOINT my_savepoint2;
    INSERT INTO table1 VALUES (3);

    -- rollback to s2
    ROLLBACK TO SAVEPOINT my_savepoint2;
    SELECT * FROM table1;               -- shows rows 1 and 2
    
    -- release s2
    RELEASE SAVEPOINT my_savepoint2;

    -- rollback to s1
    ROLLBACK TO SAVEPOINT my_savepoint1;
    SELECT * FROM table1;               -- shows only row 1
COMMIT;

1.1 执行SAVEPOINT my_savepoint;

在执行时,由ProcessUtility模块执行DefineSavepoint完成DDL(因为是第一次申请,所以申请时的事务内存上下文是TopTransactionContext)完成后走到顶层事务处理模块,走finish_xact_command->CommitTransactionCommand完成事务状态转移和收尾。

  • 【DDL】:部分执行PushTransaction函数,将当前CurrentTransactionState切换为新建的子事务TransactionState。
    • nestingLevel加一;
    • 事务块流转状态变为TBLOCK_SUBBEGIN;
  • 【CommitTransactionCommand】:完成收尾工作,在顶层内存事务内存上下文申请新的CurTransactionContext,记录在CurrentTransactionState->curTransactionContext和全局变量CurTransactionContext中。

1.2 执行ROLLBACK TO SAVEPOINT my_savepoint;

  • 【DDL】:标记事务块状态。
  • 【CommitTransactionCommand】:清理资源、标记子事务ID abort、弹出父事务、重新拉起一个同名子事务。

2 子事务ID分配

流程

  1. 事务ID都是在写发生之前申请的,savepoint语句并不会产生事务ID。
  2. 子事务ID和事务ID使用一套分配机制,区别是申请完了记录的位置不同:
  3. 普通事务ID只有一个记录在PGPROC->xid中。
  4. 子事务ID可能有多个(申请多个检查点),多个值记录在PGPROC->subxids数组中,同时每个PGPROC维护一个subxidStates,记录有多少个子事务、子事务数量是不是已经超了(最多存64个),参考下面第二张图。
  5. 注意SubTransSetParent保存的内容:用xid查找自己的父事务ID。参考这一篇《Postgresql源码(25)子事务可见性判断和性能问题》。

子事务相关数据结构:

3 子事务pg_subtrans

总结:通过xid找到parenet xid的slru数据结构,之前的很多文章提到过。参考这一篇《Postgresql源码(25)子事务可见性判断和性能问题》。

4 子事务的两阶段提交

涉及子事务的事务提交时,需要把涉及到的所有子事务全部提交掉。

按照TransactionIdSetTreeStatus函数的逻辑,如果子事务状态和顶层事务全部在一个CLOG页面,那么拿一个CLOG锁就可以搞定了。

但是如果子事务状态 和 父事务不在一个CLOG页面上,那么由于每次还是只拿一个页面的锁,操作就变成了两阶段完成,第一阶段把所有子事务改成TRANSACTION_STATUS_SUB_COMMITTED。当所有子事务都为TRANSACTION_STATUS_SUB_COMMITTED后,在修改父事务状态为TRANSACTION_STATUS_COMMITTED,然后再把子事务的状态配置为TRANSACTION_STATUS_COMMITTED。

下面是官方案例:

代码语言:javascript复制
 * Example:
 *		TransactionId t commits and has subxids t1, t2, t3, t4
 *		t is on page p1, t1 is also on p1, t2 and t3 are on p2, t4 is on p3
 *		1. update pages2-3:
 *					page2: set t2,t3 as sub-committed
 *					page3: set t4 as sub-committed
 *		2. update page1:
 *					set t1 as sub-committed,
 *					then set t as committed,
					then set t1 as committed
 *		3. update pages2-3:
 *					page2: set t2,t3 as committed
 *					page3: set t4 as committed

0 人点赞