相关 子事务的可见性判断、性能问题请看这篇:《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分配
流程
- 事务ID都是在写发生之前申请的,savepoint语句并不会产生事务ID。
- 子事务ID和事务ID使用一套分配机制,区别是申请完了记录的位置不同:
- 普通事务ID只有一个记录在PGPROC->xid中。
- 子事务ID可能有多个(申请多个检查点),多个值记录在PGPROC->subxids数组中,同时每个PGPROC维护一个subxidStates,记录有多少个子事务、子事务数量是不是已经超了(最多存64个),参考下面第二张图。
- 注意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