相关 《Postgresql源码(69)常规锁简单分析》 《Postgresql源码(73)两阶段事务PrepareTransaction事务如何与会话解绑(上)》 《Postgresql源码(74)两阶段事务PrepareTransaction事务如何与会话解绑(下)》
总结速查:
- PrepareTransaction类似于事务提交过程,因为事务提交也会将事务状态与会话解绑、做清理工作。
- 不同的是PrepareTransaction后面还要恢复信息以便二次提交,所以PrepareTransaction会保存提交所需的信息,并且将与会话关联的锁解绑,最后清理事务相关资源,达到事务与会话解绑的效果。
- 注意虽然解绑了,锁还在,只是锁与任何会话都没关系了。
1 背景
两阶段事务提供的核心能力:一阶段提交的事务保证在二阶段提交时,可以正常提交。即使一阶段提交后,数据库宕机重启,都不会影响二阶段提交。这是普通事务不具备的能力,也是分布式事务全局提交依赖的功能。
两阶段语法例子:
代码语言:javascript复制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)
- 执行完PREPARE后,事务会和当前会话“解绑”,当前会话结束事务状态,可以再起其他事务。
- 事务信息会持久化到磁盘上,如果服务器发生宕机,在启动后,也可以正常提交两阶段事务。
- 注意:锁还在(两把常规锁分别加在表和索引上,一把事务ID锁)。在上面锁表中看到
PID is null
的就是两阶段留下的锁。 - 注意:锁虽然还在,但是已经和会话无关联关系,变成了”野锁“。
2 prepare transaction
prepare transaction执行完成后,预期内要完成的事情:
- 恢复事务块状态到default初始模式。
- 保存所有使用过的、事务提交时需要的资源。
- 将保存的资源持久化:写两阶段文件
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分段分析
代码语言:javascript复制static void
PrepareTransaction(void)
{
...
事务提交准备:
代码语言:javascript复制 CallXactCallbacks(XACT_EVENT_PRE_PREPARE);
AfterTriggerEndXact(true);
PreCommit_on_commit_actions();
smgrDoPendingSyncs(true, false);
AtEOXact_LargeObject(true);
PreCommit_CheckForSerializationFailure();
停止相应中断
代码语言:javascript复制 HOLD_INTERRUPTS();
事务状态转换,记录时间点
代码语言:javascript复制 s->state = TRANS_PREPARE;
prepared_at = GetCurrentTimestamp();
构造GlobalTransactionData:MarkAsPreparing
- 加锁:TwoPhaseStateLock
- freelist中取一个Data:
gxact = TwoPhaseState->prepXacts[i];
- 构造Data、补全MYPROC、MyLockedGxact指向当前Data:
MarkAsPreparingGuts
- 现在肯定还没落盘:
gxact->ondisk = false;
- 放锁:TwoPhaseStateLock
gxact = MarkAsPreparing(xid, prepareGID, prepared_at,
GetUserId(), MyDatabaseId);
prepareGID = NULL;
收集需要保存的信息:
- 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();
上面所有收集到的信息写入XLOG。
代码语言:javascript复制 EndPrepare(gxact);
注意PostPrepare_Locks这一步非常重要:一阶段提交后,事务锁状态信息和会话也会解耦,就是这个函数做的。
PostPrepare_Locks将锁信息和PGPROC proc关联,清理LOCALLOCK锁表上的关联关系。
效果就是一阶段提交后,锁和会话的关联关系没了,锁变成了”野锁“。
代码语言:javascript复制 PostPrepare_Locks(xid);
开始事务结束的标准清理操作:
代码语言:javascript复制 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;
事务状态切回原始状态,会话与事务完成解绑
代码语言:javascript复制 s->state = TRANS_DEFAULT;
RESUME_INTERRUPTS();
}