相关 《Postgresql源码(69)常规锁简单分析》 《Postgresql源码(73)两阶段事务PrepareTransaction事务如何与会话解绑(上)》 《Postgresql源码(74)两阶段事务PrepareTransaction事务如何与会话解绑(下)》
总结速查:
两阶段事务提供的核心能力:一阶段提交的事务保证在二阶段提交时,可以正常提交。即使一阶段提交后,数据库宕机重启,都不会影响二阶段提交。这是普通事务不具备的能力,也是分布式事务全局提交依赖的功能。
两阶段语法例子:
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)
PID is null
的就是两阶段留下的锁。prepare transaction执行完成后,预期内要完成的事情:
pg_twophase/xxx
和wal日志 pg_wal/xxx
。prepare transaction命令和其他事务控制语句类似:在DDL执行中调整状态,在最后finish_xact_command->CommitTransactionCommand
时调用功能函数干活:PrepareTransaction。
static void
PrepareTransaction(void)
{
...
事务提交准备:
CallXactCallbacks(XACT_EVENT_PRE_PREPARE);
AfterTriggerEndXact(true);
PreCommit_on_commit_actions();
smgrDoPendingSyncs(true, false);
AtEOXact_LargeObject(true);
PreCommit_CheckForSerializationFailure();
停止相应中断
HOLD_INTERRUPTS();
事务状态转换,记录时间点
s->state = TRANS_PREPARE;
prepared_at = GetCurrentTimestamp();
构造GlobalTransactionData:MarkAsPreparing
gxact = TwoPhaseState->prepXacts[i];
MarkAsPreparingGuts
gxact->ondisk = false;
gxact = MarkAsPreparing(xid, prepareGID, prepared_at,
GetUserId(), MyDatabaseId);
prepareGID = NULL;
收集需要保存的信息:
StartPrepare(gxact);
AtPrepare_Notify();
AtPrepare_Locks();
AtPrepare_PredicateLocks();
AtPrepare_PgStat();
AtPrepare_MultiXact();
AtPrepare_RelationMap();
上面所有收集到的信息写入XLOG。
EndPrepare(gxact);
注意PostPrepare_Locks这一步非常重要:一阶段提交后,事务锁状态信息和会话也会解耦,就是这个函数做的。
PostPrepare_Locks将锁信息和PGPROC proc关联,清理LOCALLOCK锁表上的关联关系。
效果就是一阶段提交后,锁和会话的关联关系没了,锁变成了”野锁“。
PostPrepare_Locks(xid);
开始事务结束的标准清理操作:
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;
事务状态切回原始状态,会话与事务完成解绑
s->state = TRANS_DEFAULT;
RESUME_INTERRUPTS();
}