相关
子事务的可见性判断、性能问题请看这篇:《Postgresql源码(25)子事务可见性判断和性能问题》
子事务的DDL和数据结构请看这篇:《Postgresql源码(71)子事务数据结构与DDL分析》
测试用例
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;
SAVEPOINT my_savepoint;
在执行时,由ProcessUtility模块执行DefineSavepoint完成DDL(因为是第一次申请,所以申请时的事务内存上下文是TopTransactionContext)完成后走到顶层事务处理模块,走finish_xact_command->CommitTransactionCommand
完成事务状态转移和收尾。
CurTransactionContext
,记录在CurrentTransactionState->curTransactionContext
和全局变量CurTransactionContext
中。ROLLBACK TO SAVEPOINT my_savepoint;
流程
子事务相关数据结构:
总结:通过xid找到parenet xid的slru数据结构,之前的很多文章提到过。参考这一篇《Postgresql源码(25)子事务可见性判断和性能问题》。
涉及子事务的事务提交时,需要把涉及到的所有子事务全部提交掉。
按照TransactionIdSetTreeStatus函数的逻辑,如果子事务状态和顶层事务全部在一个CLOG页面,那么拿一个CLOG锁就可以搞定了。
但是如果子事务状态 和 父事务不在一个CLOG页面上,那么由于每次还是只拿一个页面的锁,操作就变成了两阶段完成,第一阶段把所有子事务改成TRANSACTION_STATUS_SUB_COMMITTED。当所有子事务都为TRANSACTION_STATUS_SUB_COMMITTED后,在修改父事务状态为TRANSACTION_STATUS_COMMITTED,然后再把子事务的状态配置为TRANSACTION_STATUS_COMMITTED。
下面是官方案例:
* 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