前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Postgresql源码(120)事务XID分配与主备XID同步

Postgresql源码(120)事务XID分配与主备XID同步

作者头像
mingjie
发布2024-02-05 16:50:40
1350
发布2024-02-05 16:50:40
举报

参考 《Postgresql源码(25)子事务可见性判断和性能问题》

XID获取顶层入口

函数:AssignTransactionId

代码语言:javascript
复制
static void
AssignTransactionId(TransactionState s)
{
	...

优先给没有事务ID的父事务分配

确保父事务有 XID,以便子事务总是拥有一个比其父事务更新的 XID。这里不能递归调用,否则如果我们处于一个巨大的子事务堆栈的底部,而这些子事务还没有分配 XID,可能会遇到栈溢出的问题。

下面虽然递归了,但永远不会超过1层。

代码语言:javascript
复制
	if (isSubXact && !FullTransactionIdIsValid(s->parent->fullTransactionId))
	{
		TransactionState p = s->parent;
		TransactionState *parents;
		size_t		parentOffset = 0;

		parents = palloc(sizeof(TransactionState) * s->nestingLevel);
		while (p != NULL && !FullTransactionIdIsValid(p->fullTransactionId))
		{
			parents[parentOffset++] = p;
			p = p->parent;
		}

		while (parentOffset != 0)
			AssignTransactionId(parents[--parentOffset]);

		pfree(parents);
	}

当 wal_level=logical 时,确保只有在其顶级事务的 xid 已经被记录到 WAL 中之后,子事务的 xid 才能在 WAL 流中被看到。如果必要,会记录一个包含少于PGPROC_MAX_CACHED_SUBXIDS(64个) xact_assignment 记录。

请注意,即使一个事务的didLogXid 没有被设置,而它出现在 WAL 记录中,这也是可以的,我们可能会多余地记录一些内容。当一个 xid 被包含在 wal 记录的内部某处,但不在 XLogRecord->xl_xid 中时,就会发生这种情况,如在 xl_standby_locks 中。

代码语言:javascript
复制
	if (isSubXact && XLogLogicalInfoActive() &&
		!TopTransactionStateData.didLogXid)
		log_unknown_top = true;

生成一个新的 FullTransactionId 并在 PGPROC 和 pg_subtrans 中记录其 xid。

注意:必须在 Xid 出现在 PGPROC 之外的任何共享存储中之前,先创建 subtrans 条目;因为如果 PGPROC 中没有空间容纳它,subtrans 条目是必需的,以确保其他后端将 Xid 视为“正在运行”。参见 GetNewTransactionId。

代码语言:javascript
复制
	s->fullTransactionId = GetNewTransactionId(isSubXact);
	if (!isSubXact)
		XactTopFullTransactionId = s->fullTransactionId;

	if (isSubXact)
		SubTransSetParent(XidFromFullTransactionId(s->fullTransactionId),
						  XidFromFullTransactionId(s->parent->fullTransactionId));
	
	...
	currentOwner = CurrentResourceOwner;
	CurrentResourceOwner = s->curTransactionOwner;

加xid锁,实现mvcc的功能(事务之间修改同一行数据时用到这个);resowner必须用事务的。

代码语言:javascript
复制
	XactLockTableInsert(XidFromFullTransactionId(s->fullTransactionId));

	CurrentResourceOwner = currentOwner;

在每个顶级事务中,每分配 PGPROC_MAX_CACHED_SUBXIDS(64)个事务 id,就为分配发出一个 WAL 记录。

这里看最后一章的分析:

代码语言:javascript
复制
	if (isSubXact && XLogStandbyInfoActive())
	{
		unreportedXids[nUnreportedXids] = XidFromFullTransactionId(s->fullTransactionId);
		nUnreportedXids++;

...
		if (nUnreportedXids >= PGPROC_MAX_CACHED_SUBXIDS ||
			log_unknown_top)
		{
			xl_xact_assignment xlrec;

			xlrec.xtop = GetTopTransactionId();
			Assert(TransactionIdIsValid(xlrec.xtop));
			xlrec.nsubxacts = nUnreportedXids;

			XLogBeginInsert();
			XLogRegisterData((char *) &xlrec, MinSizeOfXactAssignment);
			XLogRegisterData((char *) unreportedXids,
							 nUnreportedXids * sizeof(TransactionId));

			(void) XLogInsert(RM_XACT_ID, XLOG_XACT_ASSIGNMENT);
			nUnreportedXids = 0;
			TopTransactionStateData.didLogXid = true;
		}
	}
}

xid具体获取流程

函数:GetNewTransactionId

32位事务ID:

代码语言:javascript
复制
uint32
0 1 2 3 4 5 ...... 4294967295  →(回卷)
0 1 2 3 4 5 ...... 4294967295  →(回卷)
0 1 2 3 4 5 ...... 4294967295  →(回卷)
0 1 2 3 4 5 ...... 4294967295  →(回卷)
0 1 2 3 4 5 ...... 4294967295  →(回卷)

fullxid使用64b,分成两个32b来使用:

代码语言:javascript
复制
uint64  
[epoch 32bit][xid 32bit]

[0][0]  [0][1]  [0][2]  [0][3] ..... [0][4294967295]  →(回卷,不在溢出,只需要把epoch加一)
[1][0]  [1][1]  [1][2]  [1][3] ..... [1][4294967295]  →(回卷,不在溢出,只需要把epoch加一)
[2][0]  [2][1]  [2][2]  [2][3] ..... [2][4294967295]  →(回卷,不在溢出,只需要把epoch加一)

但是,在目前版本中(PG15),落盘的表和XLOG里面还是记录的32位XLOG,主要是兼容性问题不好解决。

代码语言:javascript
复制
typedef uint32 TransactionId;

typedef struct XLogRecord
{
	uint32		xl_tot_len;		/* total len of entire record */
	TransactionId xl_xid;		/* xact id */
	XLogRecPtr	xl_prev;		/* ptr to previous record in log */
	uint8		xl_info;		/* flag bits, see below */
	RmgrId		xl_rmid;		/* resource manager for this record */
	/* 2 bytes of padding here, initialize to zero */
	pg_crc32c	xl_crc;			/* CRC for this record */

	/* XLogRecordBlockHeaders and XLogRecordDataHeader follow, no padding */

} XLogRecord;

子事务ID维护

函数:SubTransSetParent

《Postgresql源码(25)子事务可见性判断和性能问题》

主机为什么每64个subxid就给备机发送一批?

结论

  • 主库给备库同步正在运行的xid列表,缓冲64个一批发过去,避免频率太高。
  • 备库收到后会挑一个最大的xid做记录RecordKnownAssignedTransactionIds。
  • 备库会把除了最大的都删了,因为备库可以根据最大值推测其他运行中的xid。

分析

在备机redo时,ProcArrayApplyXidAssignment负责处理收到的xids日志。

代码语言:javascript
复制
xact_redo
	...
	else if (info == XLOG_XACT_ASSIGNMENT)
	{
		xl_xact_assignment *xlrec = (xl_xact_assignment *) XLogRecGetData(record);

		if (standbyState >= STANDBY_INITIALIZED)
			ProcArrayApplyXidAssignment(xlrec->xtop,
										xlrec->nsubxacts, xlrec->xsub);
	}

包含了两部分:

  • xlrec->xtop:顶层事务ID
  • xlrec->xsub:子事务IDs

ProcArrayApplyXidAssignment函数负责处理:

代码语言:javascript
复制
ProcArrayApplyXidAssignment(TransactionId topxid,
							int nsubxids, TransactionId *subxids)
{
	...

	max_xid = TransactionIdLatest(topxid, nsubxids, subxids);

最大的子事务ID记一下就好啦,其他的备库能推测出来。

代码语言:javascript
复制
	RecordKnownAssignedTransactionIds(max_xid);

注意这里,主库的标记逻辑是

SubTransSetParent(XidFromFullTransactionId(s->fullTransactionId), XidFromFullTransactionId(s->parent->fullTransactionId));

父子事务会形成链式结构,需要查询子事务最终状态需要遍历到最后一个。

原因是要实现rollback to的逻辑,必须将事务维护成chain的结构,rollback to会按顺序影响多个子事务。

而备库的逻辑是直接全部挂在顶层事务上,因为备库redo时:

  1. 完全不关心子事务的关系,可见性判断交给快照和xid就足够了。
  2. 那怎么知道sub xid是不是提交了?
    • 情况1:sub xid的事务回滚了,那么主库会立即写clog,备库立即就知道了。
    • 情况2:sub xid的事务提交了,主库不会立即通知备库,直到顶层事务commit或release。所以这种情况下,把sub xid直接挂到顶层事务上是没有问题的,refer directly to the top-level transaction’s state。
代码语言:javascript
复制
	for (i = 0; i < nsubxids; i++)
		SubTransSetParent(subxids[i], topxid);

	...

记录完了最大的,里面其他的就可以删了。

代码语言:javascript
复制
	KnownAssignedXidsRemoveTree(InvalidTransactionId, nsubxids, subxids);

	if (TransactionIdPrecedes(procArray->lastOverflowedXid, max_xid))
		procArray->lastOverflowedXid = max_xid;
	
	...
}
本文参与?腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2024-02-04,如有侵权请联系 cloudcommunity@tencent.com 删除

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

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

本文参与?腾讯云自媒体分享计划? ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • XID获取顶层入口
  • xid具体获取流程
  • 子事务ID维护
  • 主机为什么每64个subxid就给备机发送一批?
相关产品与服务
文件存储
文件存储(Cloud File Storage,CFS)为您提供安全可靠、可扩展的共享文件存储服务。文件存储可与腾讯云服务器、容器服务、批量计算等服务搭配使用,为多个计算节点提供容量和性能可弹性扩展的高性能共享存储。腾讯云文件存储的管理界面简单、易使用,可实现对现有应用的无缝集成;按实际用量付费,为您节约成本,简化 IT 运维工作。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
http://www.vxiaotou.com