• Postgresql源码(73)两阶段事务PrepareTransaction事务如何与会话解绑


    相关
    《Postgresql源码(69)常规锁简单分析》
    《Postgresql源码(73)两阶段事务PrepareTransaction事务如何与会话解绑(上)》
    《Postgresql源码(74)两阶段事务PrepareTransaction事务如何与会话解绑(下)》

    总结速查:

    • PrepareTransaction类似于事务提交过程,因为事务提交也会将事务状态与会话解绑、做清理工作。
    • 不同的是PrepareTransaction后面还要恢复信息以便二次提交,所以PrepareTransaction会保存提交所需的信息,并且将与会话关联的锁解绑,最后清理事务相关资源,达到事务与会话解绑的效果。
    • 注意虽然解绑了,锁还在,只是锁与任何会话都没关系了。

    1 背景

    两阶段事务提供的核心能力:一阶段提交的事务保证在二阶段提交时,可以正常提交。即使一阶段提交后,数据库宕机重启,都不会影响二阶段提交。这是普通事务不具备的能力,也是分布式事务全局提交依赖的功能。

    两阶段语法例子:

    create table tbl01(i int primary key);
    insert into tbl01 values (1),(2),(3);
    
    begin;
    update tbl01 set i = 4 where i = 3;
    prepare transaction 'x1';
    
    select locktype,database,relation,transactionid,virtualtransaction,pid,mode,granted,fastpath,waitstart from pg_locks where pid is null;
       locktype    | database | relation | transactionid | virtualtransaction | pid |       mode       | granted | fastpath | waitstart 
    ---------------+----------+----------+---------------+--------------------+-----+------------------+---------+----------+-----------
     transactionid |          |          |       4003120 | 4/7843             |     | ExclusiveLock    | t       | f        | 
     relation      |    19525 |    19598 |               | 4/7843             |     | RowExclusiveLock | t       | f        | 
     relation      |    19525 |    19601 |               | 4/7843             |     | RowExclusiveLock | t       | f        | 
    
    commit prepared 'x1';
    
    select locktype,database,relation,transactionid,virtualtransaction,pid,mode,granted,fastpath,waitstart from pg_locks where pid is null;
    (0 rows)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 执行完PREPARE后,事务会和当前会话“解绑”,当前会话结束事务状态,可以再起其他事务。
    • 事务信息会持久化到磁盘上,如果服务器发生宕机,在启动后,也可以正常提交两阶段事务。
    • 注意:锁还在(两把常规锁分别加在表和索引上,一把事务ID锁)。在上面锁表中看到PID is null的就是两阶段留下的锁。
    • 注意:锁虽然还在,但是已经和会话无关联关系,变成了”野锁“。

    2 prepare transaction

    prepare transaction执行完成后,预期内要完成的事情:

    1. 恢复事务块状态到default初始模式。
    2. 保存所有使用过的、事务提交时需要的资源。
    3. 将保存的资源持久化:写两阶段文件pg_twophase/xxx 和wal日志 pg_wal/xxx

    prepare transaction命令和其他事务控制语句类似:在DDL执行中调整状态,在最后finish_xact_command->CommitTransactionCommand时调用功能函数干活:PrepareTransaction
    在这里插入图片描述

    2.1 数据结构

    • 整体结构:TwoPhaseStateData头 + max_prepared_xacts个指针 + max_prepared_xacts个两阶段状态结构
    • 头部freeGXacts连接所有空闲GlobalTransactionData
    • 头部prepXacts柔性数组便于找到GlobalTransactionData指针
    • 每开启一个两阶段事务就占用一个GlobalTransactionData指针、一个GlobalTransactionData结构

    在这里插入图片描述

    2.2 PrepareTransaction分段分析

    static void
    PrepareTransaction(void)
    {
    	...
    
    • 1
    • 2
    • 3
    • 4

    事务提交准备:

    	CallXactCallbacks(XACT_EVENT_PRE_PREPARE);
    	AfterTriggerEndXact(true);
    	PreCommit_on_commit_actions();
    	smgrDoPendingSyncs(true, false);
    	AtEOXact_LargeObject(true);
    	PreCommit_CheckForSerializationFailure();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    停止相应中断

    	HOLD_INTERRUPTS();
    
    • 1

    事务状态转换,记录时间点

    	s->state = TRANS_PREPARE;
    	prepared_at = GetCurrentTimestamp();
    
    • 1
    • 2

    构造GlobalTransactionData:MarkAsPreparing

    1. 加锁:TwoPhaseStateLock
    2. freelist中取一个Data:gxact = TwoPhaseState->prepXacts[i];
    3. 构造Data、补全MYPROC、MyLockedGxact指向当前Data:MarkAsPreparingGuts
    4. 现在肯定还没落盘:gxact->ondisk = false;
    5. 放锁:TwoPhaseStateLock
    	gxact = MarkAsPreparing(xid, prepareGID, prepared_at,
    							GetUserId(), MyDatabaseId);
    	prepareGID = NULL;
    
    • 1
    • 2
    • 3

    收集需要保存的信息:

    • StartPrepare
    • AtPrepare_Notify
    • AtPrepare_Locks:收集锁信息
    • AtPrepare_PredicateLocks
    • AtPrepare_PgStat
    • AtPrepare_MultiXact
    • AtPrepare_RelationMap
    	StartPrepare(gxact);
    
    	AtPrepare_Notify();
    	AtPrepare_Locks();
    	AtPrepare_PredicateLocks();
    	AtPrepare_PgStat();
    	AtPrepare_MultiXact();
    	AtPrepare_RelationMap();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    上面所有收集到的信息写入XLOG。

    	EndPrepare(gxact);
    
    • 1

    注意PostPrepare_Locks这一步非常重要:一阶段提交后,事务锁状态信息和会话也会解耦,就是这个函数做的。

    PostPrepare_Locks将锁信息和PGPROC proc关联,清理LOCALLOCK锁表上的关联关系。

    效果就是一阶段提交后,锁和会话的关联关系没了,锁变成了”野锁“。

    	PostPrepare_Locks(xid);
    
    
    • 1
    • 2

    开始事务结束的标准清理操作:

    	ProcArrayClearTransaction(MyProc);
    	CallXactCallbacks(XACT_EVENT_PREPARE);
    	ResourceOwnerRelease(TopTransactionResourceOwner,
    						 RESOURCE_RELEASE_BEFORE_LOCKS,
    						 true, true);
    	AtEOXact_Buffers(true);
    	AtEOXact_RelationCache(true);
    	PostPrepare_PgStat();
    	PostPrepare_Inval();
    	PostPrepare_smgr();
    	PostPrepare_MultiXact(xid);
    	PostPrepare_PredicateLocks(xid);
    	ResourceOwnerRelease(TopTransactionResourceOwner,
    						 RESOURCE_RELEASE_LOCKS,
    						 true, true);
    	ResourceOwnerRelease(TopTransactionResourceOwner,
    						 RESOURCE_RELEASE_AFTER_LOCKS,
    						 true, true);
    	PostPrepare_Twophase();
    	AtEOXact_GUC(true, 1);
    	AtEOXact_SPI(true);
    	AtEOXact_Enum();
    	AtEOXact_on_commit_actions(true);
    	AtEOXact_Namespace(true, false);
    	AtEOXact_SMgr();
    	AtEOXact_Files(true);
    	AtEOXact_ComboCid();
    	AtEOXact_HashTables(true);
    	/* don't call AtEOXact_PgStat here; we fixed pgstat state above */
    	AtEOXact_Snapshot(true, true);
    	pgstat_report_xact_timestamp(0);
    	CurrentResourceOwner = NULL;
    	ResourceOwnerDelete(TopTransactionResourceOwner);
    	s->curTransactionOwner = NULL;
    	CurTransactionResourceOwner = NULL;
    	TopTransactionResourceOwner = NULL;
    
    	AtCommit_Memory();
    
    	s->fullTransactionId = InvalidFullTransactionId;
    	s->subTransactionId = InvalidSubTransactionId;
    	s->nestingLevel = 0;
    	s->gucNestLevel = 0;
    	s->childXids = NULL;
    	s->nChildXids = 0;
    	s->maxChildXids = 0;
    
    	XactTopFullTransactionId = InvalidFullTransactionId;
    	nParallelCurrentXids = 0;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    事务状态切回原始状态,会话与事务完成解绑

    	s->state = TRANS_DEFAULT;
    
    	RESUME_INTERRUPTS();
    }
    
    • 1
    • 2
    • 3
    • 4
  • 相关阅读:
    @ConditionalOnClass编译问题
    竞赛 题目:基于机器视觉的图像矫正 (以车牌识别为例) - 图像畸变校正
    spark中的shuffle简述 那些会导致shuffle的算子
    某MDM主数据管理系统与微软Dynamic CRM系统(新加坡节点)集成案例
    Redis持久化方案 RDB,AOF
    支持目标打卡,活力三环让运动更有趣
    陀螺仪工作原理
    vue.extend 实现表格,同时编辑多条数据
    Vue3: 获取元素DOM的方法
    基线核查--渗透
  • 原文地址:https://blog.csdn.net/jackgo73/article/details/126407523