PostgreSQL数据库事务系统——获取virtual transaction id

news2025/1/16 14:06:05

如果一个事务没有进行INSERT、UPDATE、DELETE操作,那么就步会分配事务ID,但事务仍然用一个虚拟事务ID代表自己。虚拟事务ID由两部分组成,第一部分是Backend ID,另一个是每个会话自己维护的本地事务ID计数器。通过两部分组合,能保证这个虚拟事务ID的唯一性。在PostgreSQL数据库IPC——SI Message Queue中描述了Backend ID和local transaction id的产生流程。
在这里插入图片描述

StartTransaction流程中获取virtual transaction id

StartTransaction作为底层事务状态机的驱动函数,前半段函数主要是初始化TransactionState和VirtualTransactionId,其vxid.backendId就是取值自MyBackendId,vxid.localTransactionId取值自GetNextLocalTransactionId函数。VirtualXactLockTableInsert函数修改PGPROC结构体中的fpVXIDLock为true【are we holding a fast-path VXID lock?】;fpLocalTransactionId为vxid.localTransactionId【lxid for fast-path VXID lock】。

static void StartTransaction(void) {
	TransactionState s = &TopTransactionStateData;	CurrentTransactionState = s; /* Let's just make sure the state stack is empty */
	/* Set the current transaction state information appropriately during start processing.  Note that once the transaction status is switched this process cannot fail until the user ID and the security context flags are fetched below. */
	s->state = TRANS_START; s->transactionId = InvalidTransactionId;	/* until assigned */
	/* initialize current transaction state fields note: prevXactReadOnly is not used at the outermost level */
	s->nestingLevel = 1; s->gucNestLevel = 1; s->childXids = NULL; s->nChildXids = 0; s->maxChildXids = 0;
	GetUserIdAndSecContext(&s->prevUser, &s->prevSecContext); /* Once the current user ID and the security context flags are fetched, both will be properly reset even if transaction startup fails. */

	/* Make sure we've reset xact state variables
	 * If recovery is still in progress, mark this transaction as read-only. We have lower level defences in XLogInsert and elsewhere to stop us from modifying data during recovery, but this gives the normal indication to the user that the transaction is read-only. */
	if (RecoveryInProgress()){ s->startedInRecovery = true; XactReadOnly = true;
	} else { s->startedInRecovery = false; XactReadOnly = DefaultXactReadOnly; }
	XactDeferrable = DefaultXactDeferrable;XactIsoLevel = DefaultXactIsoLevel;forceSyncCommit = false;
	/* Disabled in GPDB as per comment in PrepareTransaction(). */
	seqXlogWrite = false;
	/* reinitialize within-transaction counters */
	s->subTransactionId = TopSubTransactionId;
	currentSubTransactionId = TopSubTransactionId; currentCommandId = FirstCommandId;
	currentCommandIdUsed = false; currentSavepointTotal = 0;
	fastNodeCount = 0; previousFastLink = NULL;
	/* initialize reported xid accounting */
	nUnreportedXids = 0;
	s->didLogXid = false;
	TopXactexecutorDidWriteXLog = false;
	/* must initialize resource-management stuff first */
	AtStart_Memory(); AtStart_ResourceOwner();

    VirtualTransactionId vxid;
	/* Assign a new LocalTransactionId, and combine it with the backendId to form a virtual transaction id. */
	vxid.backendId = MyBackendId;
	vxid.localTransactionId = GetNextLocalTransactionId();

	/*
	 * Lock the virtual transaction id before we announce it in the proc array
	 */
	VirtualXactLockTableInsert(vxid);

	/*
	 * Advertise it in the proc array.  We assume assignment of
	 * LocalTransactionID is atomic, and the backendId should be set already.
	 */
	Assert(MyProc->backendId == vxid.backendId);
	MyProc->lxid = vxid.localTransactionId;

	TRACE_POSTGRESQL_TRANSACTION_START(vxid.localTransactionId);

在这里插入图片描述

VirtualXactLock

平时查询锁信息时,执行select * from pg_locks,如果会话已经没有当前打开的事务,则连接不会创建事务,而是隐式发出。查询select * from pg_locks不能是中立的观察者,因为它需要自己使用事务和锁定pg_locks。因此,此即使数据库没有其他活跃连接,该查询​​始终报告至少两个条目,如下例所示。我们看第二个条码其锁类型就是virtualxid,锁模式是ExclusiveLock。

test=> \x
test=> SELECT relation::regclass AS relname, * FROM pg_locks;

-[ RECORD 1 ]------+----------------
relname            | pg_locks
locktype           | relation
database           | 113270
relation           | 11000
page               | 
tuple              | 
virtualxid         | 
transactionid      | 
classid            | 
objid              | 
objsubid           | 
virtualtransaction | 2/5789
pid                | 31376
mode               | AccessShareLock
granted            | t
-[ RECORD 2 ]------+----------------
relname            | 
locktype           | virtualxid
database           | 
relation           | 
page               | 
tuple              | 
virtualxid         | 2/5789
transactionid      | 
classid            | 
objid              | 
objsubid           | 
virtualtransaction | 2/5789
pid                | 31376
mode               | ExclusiveLock
granted            | t

VirtualXactLockTableInsert函数通过fast-path获取vxid锁(Take vxid lock via the fast-path)。 There can’t be any pre-existing lockers, as we haven’t advertised this vxid via the ProcArray yet. Since MyProc->fpLocalTransactionId will normally contain the same data as MyProc->lxid, you might wonder if we really need both. The difference is that MyProc->lxid is set and cleared unlocked, and examined by procarray.c, while fpLocalTransactionId is protected by backendLock and is used only by the locking subsystem. Doing it this way makes it easier to verify that there are no funny race conditions. We don’t bother recording this lock in the local lock table, since it’s only ever released at the end of a transaction. Instead, LockReleaseAll() calls VirtualXactLockTableCleanup(). 不可能有任何预先存在的lockers,因为我们还没有通过ProcArray声明这个vxid。由于MyProc->fpLocalTransactionId通常包含与MyProc->lxid相同的数据,您可能会怀疑我们是否真的需要两者。不同的是,MyProc->lxid被设置并清除为解锁,并由procarray.c检查,而fpLocalTransactionId受backendLock保护,仅由锁定子系统(the locking subsystem)使用。这样做可以更容易地验证是否存在有趣的比赛条件。我们不需要在本地锁表中记录这个锁,因为它只在事务结束时释放。相反,LockReleaseAll()调用VirtualXactLockTableCleanup()。

void VirtualXactLockTableInsert(VirtualTransactionId vxid) {
	LWLockAcquire(MyProc->backendLock, LW_EXCLUSIVE);
	MyProc->fpVXIDLock = true; MyProc->fpLocalTransactionId = vxid.localTransactionId;
	LWLockRelease(MyProc->backendLock);
}

VirtualXactLock函数将形式上的vxid锁被转换成了main lock table。如果wait为true,则函数会等待直到给定的VXID被释放,然后返回true;如果wait为false,仅仅检查VXID是否还在运行,然后返回true或者false。该函数执行的流程如下:

  1. 首先通过vxid.backendId获取其对应的PGPROC,如果PGPROC为null,说明该后端进程已经结束,直接返回true。
  2. 获取proc的backendLock,检查请求的vxid是否为查找到的proc对应的vxid。如果不同,说明该后端进程已经结束,直接返回true。
  3. 如果指定wait为false,就是上述流程,仅仅检查VXID对应的后端进程是否还在运行,返回false,说明还在运行。
  4. 如果指定wait为true,需要将fast-path lock形式锁转换为常规锁【we’re going to need to sleep on the VXID. But first, we must set up the primary lock table entry, if needed (ie, convert the proc’s fast-path lock on its VXID to a regular lock)】,并将proc->fpVXIDLock清理。利用LockAcquire(&tag, ShareLock, false, false)函数抢占该常规锁,抢占到后即进行释放,最后直接返回true,说明该进程不在了。
/*		VirtualXactLock
 * If wait = true, wait until the given VXID has been released, and then return true.
 * If wait = false, just check whether the VXID is still running, and return true or false. */
bool VirtualXactLock(VirtualTransactionId vxid, bool wait) {
	LOCKTAG		tag; SET_LOCKTAG_VIRTUALTRANSACTION(tag, vxid);

	/* If a lock table entry must be made, this is the PGPROC on whose behalf it must be done.  Note that the transaction might end or the PGPROC might be reassigned to a new backend before we get around to examining it, but it doesn't matter.  If we find upon examination that the relevant lxid is no longer running here, that's enough to prove that it's no longer running anywhere. */
	PGPROC	   *proc = BackendIdGetProc(vxid.backendId);
	if (proc == NULL) return true;

	/* We must acquire this lock before checking the backendId and lxid against the ones we're waiting for.  The target backend will only set or clear lxid while holding this lock. */
	LWLockAcquire(proc->backendLock, LW_EXCLUSIVE);
	/* If the transaction has ended, our work here is done. */
	if (proc->backendId != vxid.backendId || proc->fpLocalTransactionId != vxid.localTransactionId) {
		LWLockRelease(proc->backendLock); return true;
	}
	/* If we aren't asked to wait, there's no need to set up a lock table entry.  The transaction is still in progress, so just return false. */
	if (!wait) {
		LWLockRelease(proc->backendLock); return false;
	}

	/* OK, we're going to need to sleep on the VXID.  But first, we must set up the primary lock table entry, if needed (ie, convert the proc's fast-path lock on its VXID to a regular lock). */
	if (proc->fpVXIDLock){
		PROCLOCK   *proclock;uint32		hashcode;LWLock	   *partitionLock;
		hashcode = LockTagHashCode(&tag);partitionLock = LockHashPartitionLock(hashcode);
		LWLockAcquire(partitionLock, LW_EXCLUSIVE);
		proclock = SetupLockInTable(LockMethods[DEFAULT_LOCKMETHOD], proc, &tag, hashcode, ExclusiveLock);
		if (!proclock){
			LWLockRelease(partitionLock);LWLockRelease(proc->backendLock);
			ereport(ERROR,(errcode(ERRCODE_OUT_OF_MEMORY), errmsg("out of shared memory"), errhint("You might need to increase max_locks_per_transaction.")));
		}
		GrantLock(proclock->tag.myLock, proclock, ExclusiveLock);
		LWLockRelease(partitionLock);
		proc->fpVXIDLock = false;
	}
	/* Done with proc->fpLockBits */
	LWLockRelease(proc->backendLock);

	/* Time to wait. */
	(void) LockAcquire(&tag, ShareLock, false, false);
	LockRelease(&tag, ShareLock, false);
	return true;
}

VirtualXactLockTableCleanup函数Check whether a VXID lock has been materialized; if so, release it, unblocking waiters. 首先清理VirtualXactLockTableInsert设置的两个成员;如果fastpath为false,lxid为有效的本地事务id,说明形式上的vxid锁被转换成了main lock table,需要调用LockRefindAndRelease函数解锁。

void VirtualXactLockTableCleanup(void) {
	/* Clean up shared memory state. */
	LWLockAcquire(MyProc->backendLock, LW_EXCLUSIVE);
	bool fastpath = MyProc->fpVXIDLock; LocalTransactionId lxid = MyProc->fpLocalTransactionId;
	MyProc->fpVXIDLock = false; MyProc->fpLocalTransactionId = InvalidLocalTransactionId;
	LWLockRelease(MyProc->backendLock);

	/* If fpVXIDLock has been cleared without touching fpLocalTransactionId, that means someone transferred the lock to the main lock table. */
	if (!fastpath && LocalTransactionIdIsValid(lxid)) {
		VirtualTransactionId vxid; LOCKTAG		locktag;
		vxid.backendId = MyBackendId; vxid.localTransactionId = lxid;
		SET_LOCKTAG_VIRTUALTRANSACTION(locktag, vxid);
		
		LockRefindAndRelease(LockMethods[DEFAULT_LOCKMETHOD], MyProc, &locktag, ExclusiveLock, false);
	}
}
#define SET_LOCKTAG_VIRTUALTRANSACTION(locktag,vxid) \
	((locktag).locktag_field1 = (vxid).backendId,  (locktag).locktag_field2 = (vxid).localTransactionId,  (locktag).locktag_field3 = 0, (locktag).locktag_field4 = 0,  (locktag).locktag_type = LOCKTAG_VIRTUALTRANSACTION,  (locktag).locktag_lockmethodid = DEFAULT_LOCKMETHOD)

VirtualXactLockTableInsert在StartTransaction和StarupXLOG处调用,就是为了获取virtual transaction id,然后设置fast-path lock形式上的锁。从下图可以看出GetLockStatusData也是依据fast-path lock来组装LockInstanceData,然后可以通过查询pg_locks来查看该条LockInstanceData。
在这里插入图片描述
调用VirtualXactLock的函数,其中WaitForLockersMultiple和DefineIndex是为了确保用于指定vxid的进程已经退出,而ResolveRecoveryConflictWithVirtualXIDs仅仅是为了检查指定vxid进程是否退出。DefineIndex函数使用VirtualXactLock是为了The index is now valid in the sense that it contains all currently interesting tuples. But since it might not contain tuples deleted just before the reference snap was taken, we have to wait out any transactions that might have older snapshots. Obtain a list of VXIDs of such transactions, and wait for them individually。WaitForLockersMultiple函数主要用于等待直到指定锁类型和锁模式的锁上没其他事务抢占【Wait until no transaction holds locks that conflict with the given locktags at the given lockmode.】,使用VirtualXactLock实现等待指定vxid对应后端进程结束的功能。ResolveRecoveryConflictWithVirtualXIDs函数使用VirtualXactLock函数用于确保和recovery进程冲突的后端进程都已经结束。

StartTransaction --> VirtualXactLockTableInsert
StarupXLOG --> InitRecoveryTransactionEnvironment --> VirtualXactLockTableInsert

WaitForLockers --> WaitForLockersMultiple --> VirtualXactLock(xxx, true)
ResolveRecoveryConflictWithSnapshot/ResolveRecoveryConflictWithTablespace/ResolveRecoveryConflictWithLock --> ResolveRecoveryConflictWithVirtualXIDs --> VirtualXactLock(xxx, false)
DefineIndex --> VirtualXactLock(xxx, true)

LockReleaseAll --> VirtualXactLockTableCleanup
StarupXLOG --> ShutdownRecoveryTransactionEnvironment --> VirtualXactLockTableCleanup

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/998219.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

算法通关村第十九关——动态规划高频问题(白银)

算法通关村第十九关——动态规划高频问题(白银) 前言1 最少硬币数2 最长连续递增子序列3 最长递增子序列4 完全平方数5 跳跃游戏6 解码方法7 不同路径 II 前言 摘自:代码随想录 动态规划五部曲: 确定dp数组(dp tabl…

Unicode:Codejock Suite Prov22.1.0 for ActiveX Crack

Codejock 荣获 2022 年 Visual Studio 杂志读者选择奖!Visual Studio 杂志宣布了第 28 届年度读者选择奖的获奖者。今年是 Visual Studio 杂志的读者对其 Visual Studio 相关工具和服务进行投票的第 28 年,他们对 42 个类别的产品提供了意见,…

Windows开启linux子系统并迁移到非系统盘

windows用户如想使用linux有一个非常简单的方法,就是开启windows下的linux子系统,也叫做WSL(Windows Subsystem for Linux),下面简述一下安装过程。 首先打开控制面板,程序功能下面的开启或关闭windows功能 然后再下面这个界面勾…

【C刷题】day1

一、选择题 1.正确的输出结果是 int x5,y7; void swap() { int z; zx; xy; yz; } int main() { int x3,y8; swap(); printf("%d,%d\n",x, y); return 0; } 【答案】: 3,8 【解析】: 考点: &#xff…

Redis使用原生命令搭建集群

1.Redis版本及下载 找到安装的redis版本,redis3.0以上版本才支持集群 下载对应的版本 2.安装redis集群 解压上传编译 [hadoophost152 opensource]$ tar -xvf redis-3.2.11.tar.gz [hadoophost152 opensource]$ cd redis-3.2.11/ [hadoophost152 redis-3.2.11]$ ma…

数据结构 每日一练:将带头结点的单链表就地逆置(视频讲解两种方法)

目录 方法一 算法视频分析 方法二 算法视频分析 Q:什么是“就地”捏? A:就是指辅助空间复杂度为O(1),通俗一点来说就是不需要再开辟一块空间来实现算法。 特别说明: 笔者第一次录制视频,言语有些不顺&…

支持事务的分布式NoSQL——FoundationDB

【引子】周末阅读时光,一篇好的论文(https://cacm.acm.org/magazines/2023/6/273229-foundationdb-a-distributed-key-value-store/fulltext),开阔了眼界,支持事务语义的NoSQL应该放到软件系统架构备选方案之中。 Foun…

Kotlin 协程 - 生命周期 Job

一、概念 对于每一个由协程构建器开启的协程,都会返回一个 Job 实例用来管理协程的生命周期。launch()直接返回 Job实现,async() 返回的 Deferred 实现了 Job接口。 Job public fun start(): Boolean public fun cancel(cause: CancellationException? …

java中HashMap如何根据value的值去获取key是多少

在Java中&#xff0c;HashMap是一种基于键值对存储数据的数据结构。HashMap并没有直接提供根据value获取key的方法。但你可以通过遍历HashMap的entrySet&#xff0c;找到对应的value&#xff0c;然后获取其对应的key。 以下是一个示例代码&#xff1a; public <K, V> K…

计算机图形学环境配置java3D

计算机图形学环境配置java3D JDK18&#xff08;或者一些版本都无法支持Applet类&#xff09;idea配置导入java3D的jar包测试代码&#xff1a;运行效果&#xff1a; java3Dwindows64位下载 这个是默认到下图路径中&#xff1a;&#xff08;记住这个路径&#xff0c;待会要导入ja…

2023Web前端开发面试手册

​​​​​​​​ HTML基础 1. HTML 文件中的 DOCTYPE 是什么作用&#xff1f; HTML超文本标记语言: 是一个标记语言, 就有对应的语法标准 DOCTYPE 即 Document Type&#xff0c;网页文件的文档类型标准。 主要作用是告诉浏览器的解析器要使用哪种 HTML规范 或 XHTML规范…

【C++笔记】C++STL vector类模拟实现

【C笔记】CSTL vector类模拟实现 一、实现模型和基本接口1.1、各种构造和析构1.2、迭代器 二、各种插入和删除接口2.1、插入接口2.1、删除接口2.3、resize接口 三、运算符重载3.1、方括号运算符重载3.2、赋值运算符重载 一、实现模型和基本接口 实现模型我们选择模拟库中的模型…

企业架构LNMP学习笔记35

学习目标和内容&#xff1a; 1、能够通过HAproxy实现负载均衡。 2、安装&#xff1a; yum install -y haproxy 3、配置文件修改点&#xff1a; 修改为80&#xff0c;并将后面几个用不到的&#xff0c;都进行删除。 代理转发到后端的app端。 4、后端app端的定义&#xff1b; …

第10章_瑞萨MCU零基础入门系列教程之中断控制单元简介

本教程基于韦东山百问网出的 DShanMCU-RA6M5开发板 进行编写&#xff0c;需要的同学可以在这里获取&#xff1a; https://item.taobao.com/item.htm?id728461040949 配套资料获取&#xff1a;https://renesas-docs.100ask.net 瑞萨MCU零基础入门系列教程汇总&#xff1a; ht…

【图论】SPFA求负环

算法提高课笔记 文章目录 基础知识例题虫洞题意思路代码 观光奶牛题意思路代码 单词环题意思路代码 基础知识 负环&#xff1a;环上权值之和是负数 求负环的常用方法 基于SPFA 统计每个点入队次数&#xff0c;如果某个点入队n次&#xff0c;则说明存在负环&#xff08;完全…

OSPF路由计算

1、Router LSA LSA 链路状态通告&#xff0c;是OSPF进行路由计算的主要依据&#xff0c;在OSPF的LSU报文中携带&#xff0c;其头重要字段及解释&#xff1a; LS Type&#xff08;链路状态类型&#xff09;&#xff1a;指示本LSA的类型。 在域内、域间、域外…

upload-labs/Pass-07 未知后缀名解析漏洞复现

upload-labs/Pass-07 漏洞复现 页面&#xff1a; 我们看到有一个图片上传功能。 我们上传一个png文件发现能够成功上传&#xff0c;那其他文件呢&#xff0c;如php文件。 我们看一下是否能上传一个php文件&#xff1a; php文件内容&#xff1a; <?phpeval($_REQUEST[]…

计算机系统的基本概念

计算机系统的基本概念 本文主要以hello.c这个程序的整个生命周期来简单了解一下计算机系统结构的基本概念。 #include <stdio.h>int main() {printf("hello, world\n");return 0; }gcc hello.c -o hello ./hello hello, world此刻&#xff0c;hello.c源程序…

运算符,switch

目录 算术运算符 逻辑运算符 强制类型转换 自增自减运算符 ​编辑 三目运算符 A&#xff1f;B:C 逗号表达式 switch 算术运算符 除法的运算结果和运算对象的数据类型有关&#xff0c;两个都是int商就是int&#xff0c;被除数或者除数只要有一个是浮点型数据&#xff0c;…

ARM DIY(十一)板子名称、开机 logo、LCD 控制台、console 免登录、命令提示符、文件系统大小

文章目录 前言板子名称uboot Modelkernel 欢迎词、主机名 开机 logoLCD 控制台console 免登录命令提示符文件系统大小 前言 经过前面十篇文章的介绍&#xff0c;硬件部分调试基本完毕&#xff0c;接下来的文章开始介绍软件的个性化开发。 板子名称 uboot Model 既然是自己的…