前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Postgresql源码(73)两阶段事务PrepareTransaction事务如何与会话解绑(上)

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

作者头像
mingjie
发布2022-09-23 10:21:50
4610
发布2022-09-23 10:21:50
举报
文章被收录于专栏:Postgresql源码分析

相关 《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执行完成后,预期内要完成的事情:

  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分段分析

代码语言: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

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

收集需要保存的信息:

  • StartPrepare
  • AtPrepare_Notify
  • AtPrepare_Locks:收集锁信息
  • AtPrepare_PredicateLocks
  • AtPrepare_PgStat
  • AtPrepare_MultiXact
  • AtPrepare_RelationMap
代码语言:javascript
复制
	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();
}
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-08-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1 背景
  • 2 prepare transaction
    • 2.1 数据结构
      • 2.2 PrepareTransaction分段分析
      相关产品与服务
      分布式事务 DTF
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档