pg事务:可见性检查

news/2024/7/9 22:33:14 标签: postgresql, 事务, 可见性检查

快照有了,就可以通过快照数据去判断元组的可见性。回顾一下(先不考虑子事务),事务的关键信息:元组头部事务信息、快照信息、clog事务状态(SetHintBits前需要)

  • 元组上有元组xmin、xmax、cmin、cmax、infomask等
  • 快照数据中有快照xmin、xmax、xip_list、curcid等
  • clog中额外的事务状态信息,也可能写入了infomask中的hintbits

快照类型不同,可见性判断略有区别

bool
HeapTupleSatisfiesVisibility(HeapTuple tup, Snapshot snapshot, Buffer buffer)
{
	switch (snapshot->snapshot_type)
	{
		case SNAPSHOT_MVCC:
			return HeapTupleSatisfiesMVCC(tup, snapshot, buffer);
			break;
		...
		case SNAPSHOT_NON_VACUUMABLE:
			return HeapTupleSatisfiesNonVacuumable(tup, snapshot, buffer);
			break;
	}
	...
}

每种快照都有各自的可见性规则,这里用最常见的SNAPSHOT_MVCC快照可见性规则来理解元组可见性

static bool
HeapTupleSatisfiesMVCC(HeapTuple htup, Snapshot snapshot,
					   Buffer buffer)
{
	HeapTupleHeader tuple = htup->t_data;  

	Assert(ItemPointerIsValid(&htup->t_self));  //lp有效,也就是元组有效
	Assert(htup->t_tableOid != InvalidOid);   //oid有效,也就是表还有效

	//t_xmin未提交,insert或update新元组的事务,未提交
	//在htup_details.h中已经宏定过,HeapTupleHeaderXminCommitted()是((tup)->t_infomask & HEAP_XMIN_COMMITTED) != 0
	//也就是说if (!HeapTupleHeaderXminCommitted(tuple)) 表示元组infomask中没有HEAP_XMIN_COMMITTED
	//其实就是字面含义,t_xmin没有提交
	if (!HeapTupleHeaderXminCommitted(tuple)) 
	{
		//假如有个事务更新了元组,但是回退或者失败了,那么这个元组的xmin就是失败的事务ID
		//如果是失败事务的t_xmin,则直接返回不可见
		if (HeapTupleHeaderXminInvalid(tuple))
			return false;
		
		//当元组infomask有HEAP_MOVED_OFF标记时,vacuum元组单独判断可见性,并对于vacuum事务做一些hintbits标记
		/* Used by pre-9.0 binary upgrades */
		if (tuple->t_infomask & HEAP_MOVED_OFF)
		{
			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);

			if (TransactionIdIsCurrentTransactionId(xvac))
				return false;
			if (!XidInMVCCSnapshot(xvac, snapshot))
			{
				if (TransactionIdDidCommit(xvac))
				{
					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
								InvalidTransactionId);
					return false;
				}
				SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
							InvalidTransactionId);
			}
		}
		//当元组infomask有HEAP_MOVED_IN标记时,vacuum元组单独判断可见性,并对于vacuum事务做一些hintbits标记
		/* Used by pre-9.0 binary upgrades */
		else if (tuple->t_infomask & HEAP_MOVED_IN)
		{
			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);

			if (!TransactionIdIsCurrentTransactionId(xvac))
			{
				if (XidInMVCCSnapshot(xvac, snapshot))
					return false;
				if (TransactionIdDidCommit(xvac))
					SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
								InvalidTransactionId);
				else
				{
					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
								InvalidTransactionId);
					return false;
				}
			}
		}
		//当元组是本事务写入的
		else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tuple)))
		{
			if (HeapTupleHeaderGetCmin(tuple) >= snapshot->curcid) //当元组cid>=快照事务当前commandid时
				return false;	// 说明插入元组的时间晚于可见性检测开始时间,元组不可见

			if (tuple->t_infomask & HEAP_XMAX_INVALID) //当元组infomask位有HEAP_XMAX_INVALID时
				return true;   //说明元组没有被删除(delete),元组可见
				//因为仅插入元组,不提交、提交或者回退,都是HEAP_XMAX_INVALID
				//但是这个判断在“本事物写入”条件下,所以这里的逻辑是
				//本事务新增元组,未提交(逻辑上等价于同一事务中元组未被删除),且t_cid<curcid,元组可见

			//xmax在两种情况被设置:1对元组加锁,元组被删除
			//即使元组没有HEAP_XMAX_INVALID,可能也不是被删除,也可能是元组加锁了
			//元组加锁情况下设置了xmax,元组可见
			if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))	/* not deleter */
				return true;
			
			//  HEAP_XMAX_IS_MULTI是对多个事务获取同一行锁时,才会产生MultiXactId
			// 这里仍然是在判断xmax加锁情况下的可见性
			if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
			{
				TransactionId xmax;

				xmax = HeapTupleGetUpdateXid(tuple);

				/* not LOCKED_ONLY, so it has to have an xmax */
				Assert(TransactionIdIsValid(xmax));

				/* updating subtransaction must have aborted */
				//如果xmax不是当前事务,则可见
				if (!TransactionIdIsCurrentTransactionId(xmax))
					return true;
				//如果xmax是当前事务,通过commandid判断,在更新和删除操作之前获得快照,在获得快照时间元组是可见的
				else if (HeapTupleHeaderGetCmax(tuple) >= snapshot->curcid)
					return true;	/* updated after scan started */
				else
					return false;	/* updated before scan started */
			}
			
			//以下判断场景是:子事务中的删除命令回退了,需要SetHintBits为HEAP_XMAX_INVALID
			//删除命令回退了,所以元组是可见的
			if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
			{
				/* deleting subtransaction must have aborted */
				SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
							InvalidTransactionId);
				return true;
			}

			//cmax删除元组的cid
			//如果元组cmax>=快照curcid,则删除发生在快照扫描之后,元组可见
			//如果元组cmax<快照curcid,则删除发生在快照扫描之前,元组不可见
			if (HeapTupleHeaderGetCmax(tuple) >= snapshot->curcid)
				return true;	/* deleted after scan started */
			else
				return false;	/* deleted before scan started */
		}
		//XidInMVCCSnapshot()判断xid在快照生成时是in-progress状态
		//in-progress是 1.快照xmin<=xid<快照xmax且xid in xip_list  2.xid>=快照xmax
		//以下XidInMVCCSnapshot()中的xid是t_xmin
		//所以,这个判断的含义是:如果t_xmin在快照生成时是in-progress状态,则元组不可见
		//相当于t_xmin没有提交,所以元组不可见。这看上去有点奇怪
		//因为整个判断是在!HeapTupleHeaderXminCommitted(tuple)下的,含义也是t_xmin没有提交,判断有些重复
		//但是加上前面的几个小判断,这的else if就变得合理,其含义为:
		//t_xmin没有提交,且元组没有被删除,且不是当前事务,则元组不可见
		else if (XidInMVCCSnapshot(HeapTupleHeaderGetRawXmin(tuple), snapshot))
			return false;
			
		//如果t_xmin事务提交了,做个SetHintBits为HEAP_XMIN_COMMITTED
		//这里看上去有点奇怪,整个判断是t_xmin未提交的情况下,不应该出现t_xmin提交了
		//而且,真的有这种情况的话,为什么这里不做事务可见性判断?
		else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple)))
			SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
						HeapTupleHeaderGetRawXmin(tuple));
		//如果t_xmin事务没有提交,做个SetHintBits为HEAP_XMIN_INVALID
		else
		{
			/* it must have aborted or crashed */
			SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
						InvalidTransactionId);
		//t_xmin事务没有提交,再次返回不可见。看上去跟上面的关于XidInMVCCSnapshot()的条件很像
		//当前即没有提交,又不满足XidInMVCCSnapshot()(xid在快照生成时不是in-progress)
		//只有生成快照时事务未开始,后来事务开始了,目前没有提交,元组不可见
		return false;  
		}
	}
	//xmin未提交的情况下的事务可见性总算判断完了
	//这里的else以后都是xmin提交的情况下,对元组可见性的判断
	//xmin已提交的判断是hintbits位有HEAP_XMIN_COMMITTED
	else
	{
		//xmin已提交,但是不是当前快照做的
		/* xmin is committed, but maybe not according to our snapshot */
		//当infomask没有HEAP_XMIN_FROZEN且在快照生成时xmin in-progress状态,则元组不可见
		//整合翻译一下,这里的if含义为快照生成时xmin未提交,在可见性判断时,元组xmin提交但没有标记FROZEN的情况,元组不可见
		//即使元组xmin已提交了,对当前快照而言仍然是in-progress
		if (!HeapTupleHeaderXminFrozen(tuple) &&
			XidInMVCCSnapshot(HeapTupleHeaderGetRawXmin(tuple), snapshot))
			return false;		/* treat as still in progress */
	}

	//HEAP_XMAX_INVALID表示元组没有被删除
	//这里的if含义是:当前元组提交了且在快照生成时也提交了且没有被删除(完全没有删除标记),元组可见
	if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid or aborted */
		return true;
	
	//虽然元组上有xmax,但不是删除事务,而是锁标记
	//这里的if含义是:当前元组提交了且在快照生成时也提交了且有xmax,但xmax是锁标记,元组可见
	if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
		return true;
	
	//HEAP_XMAX_IS_MULTI表示元组处于shared-row-lock,一般表示多个事务处理一行时行所拥有的infomask标记
	if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
	{
		TransactionId xmax;

		/* already checked above */
		Assert(!HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask));

		//获取更新元组的事务id
		xmax = HeapTupleGetUpdateXid(tuple);

		/* not LOCKED_ONLY, so it has to have an xmax */
		Assert(TransactionIdIsValid(xmax));

		//如果shared-row-lock元组的事务ID是当前事务
		if (TransactionIdIsCurrentTransactionId(xmax))
		{
			//元组cmax>=快照curcid时,元组在快照生成时还没有被删除,元组可见
			if (HeapTupleHeaderGetCmax(tuple) >= snapshot->curcid)
				return true;	/* deleted after scan started */
			//元组cmax<快照curcid时,元组在快照生成时还已被删除,元组可见
			else
				return false;	/* deleted before scan started */
		}
		//如果shared-row-lock元组的事务ID不是当前事务,且在快照生成时xmax处于in-progress状态
		//这里的if含义是:xmin提交,且元组未被删除,有MULTI XMAX标记的情况下,xmax在快照生成时还未提交,元组可见
		if (XidInMVCCSnapshot(xmax, snapshot))
			return true;
		//如果shared-row-lock元组事务已提交,则元组不可见
		if (TransactionIdDidCommit(xmax))
			return false;		/* updating transaction committed */
		/* it must have aborted or crashed */
		//更新元组异常终止或回滚,元组仍可见
		return true;
	}
	
	//元组xmin已提交,xmax还没有提交标记,还没有被删除
	//看来!HEAP_XMAX_COMMITTED和HEAP_XMAX_INVALID还是有点区别
	//这里的判断看上去像元组经历过删除,但删除事务没有提交
	//而上面的HEAP_XMAX_INVALID是完全确认没有删除元组或删除abort or rollback,所以可以直接判断为true
	if (!(tuple->t_infomask & HEAP_XMAX_COMMITTED))
	{
		//如果xmax与校验事务是同一事物
		if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
		{
			//还是老一套通过commandid检测可见性
			//cmax>=快照curcid,说明删除在快照生成之后,元组可见
			if (HeapTupleHeaderGetCmax(tuple) >= snapshot->curcid)
				return true;	/* deleted after scan started */
				
			//cmax<快照curcid,说明删除在快照生成之前,元组不可见
			else
				return false;	/* deleted before scan started */
		}
		
		//删除事务没有提交,且xmax与校验事务不是同一事物
		//xmax在生成快照时是in-progress的,则元组可见
		if (XidInMVCCSnapshot(HeapTupleHeaderGetRawXmax(tuple), snapshot))
			return true;
		
		//确认xmax删除事务回退或失败,SetHintBits为HEAP_XMAX_INVALID
		//类似上面的HEAP_XMAX_INVALID,元组可见
		if (!TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple)))
		{
			/* it must have aborted or crashed */
			SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
						InvalidTransactionId);
			return true;
		}

		/* xmax transaction committed */
		//还剩下xmax删除事务已提交的场景,SetHintBits为HEAP_XMAX_COMMITTED
		//按道理这里应该判断可见性,但是没有写在这,而是在代码的最后几行。因为这里的小条件属于大条件之一
		SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED,
					HeapTupleHeaderGetRawXmax(tuple));
	}
	else
	{
		/* xmax is committed, but maybe not according to our snapshot */
		//xmax删除事务当前已提交,但是在快照生成时in-progress,则元组可见
		if (XidInMVCCSnapshot(HeapTupleHeaderGetRawXmax(tuple), snapshot))
			return true;		/* treat as still in progress */
	}

	/* xmax transaction committed */
	//只剩下xmax已提交的,且不在快照生成时in-progress的情况,元组不可见
	return false;
}

整个可见性判断的源码看上去有点复杂。把SetHintBits部分刨去,省略繁杂的if,只看关键性的可见性规则,其中的关键点如下:

  1. 可见性规则的主干逻辑

    • 删除已提交,元组不可见
    • 插入已提交,删除回滚,元组可见
    • 插入已提交,删除未提交,本事务需要对比cid,其他事务对元组可见
    • 插入回滚,元组不可见
    • 插入未提交,同事务需要对比cmin,其他事务对元组不可见
  2. 可见性检查中有两个时间点,一个是检查发生时间,一个是快照生成时间。判断逻辑就有同一个事务(检查事务和快照生成事务为同一事务)和不同事务(检查事务和快照生成事务为2个不同的事务)的情况

    • 如果是同一事务,通过对比元组cmin/cmax和snapshot->curcid的大小

      cmin>=snapshot->curcid,说明插入元组的事务晚于快照获取时间,此时元组不可见。反之元组可见

      cmax>=snapshot->curcid,说明删除元组的事务晚于快照获取时间,此时元组可见。反之元组不可见

    • 如果不是同一事务,通过XidInMVCCSnapshot()函数判断xid(t_xmin或t_xmax)是否在快照生成时in-progress

      xmin在快照生成时in-progress,则元组不可见

      xmax在快照生成时in-progress,则元组可见

  3. 除了基本的dml操作,还需要完善其他条件下的判断,有如下4种情况:

    • vacuum事务删除和新增的元组可见性判断

    • HEAP_XMAX_IS_LOCKED_ONLY锁标记时元组可见

    • HEAP_XMAX_IS_MULTI元组处于multixact状态时的可见性判断

    • 元组有frozen标记时的可见性判断


http://www.niftyadmin.cn/n/342154.html

相关文章

【LeetCode: 44. 通配符匹配 | 暴力递归=>记忆化搜索=>动态规划 】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

29-jQuery-选择器

一、基本选择器 1、标签选择器&#xff08;元素选择器&#xff09; 元素选择器可以选取HTML文档中所有匹配的元素。例如&#xff0c;如果要选取所有段落元素&#xff08;p&#xff09; $("p") //这将返回页面中所有的段落元素。 2、id选择器 id选择器根据元素的…

【C++模板】——template

C模板 模板的引入函数模板函数模板的实例化模板参数匹配原则 类模板类模板的定义格式类模板的实例化 &#x1f340;小结&#x1f340; &#x1f389;博客主页&#xff1a;小智_x0___0x_ &#x1f389;欢迎关注&#xff1a;&#x1f44d;点赞&#x1f64c;收藏✍️留言 &#x1…

【转载】《如何超过大多数人》——陈皓(左耳朵耗子)

转载自&#xff1a;酷 壳 – CoolShell 当你看到这篇文章的标题&#xff0c;你一定对这篇文章产生了巨大的兴趣&#xff0c;因为你的潜意识在告诉你&#xff0c;这是一本人生的“武林秘籍”&#xff0c;而且还是左耳朵写的&#xff0c;一定有干货满满&#xff0c;只要读完&…

数据结构—排序算法(插入排序及选择排序)

目录 1、排序的概念 2、常见的排序算法 3、直接插入排序&#xff08;插入排序&#xff09; 3.1 直接插入排序基本思想 3.2 直接插入排序实现 4、 希尔排序( 缩小增量排序 )&#xff08;插入排序&#xff09; 4.1 基本思想 4.2 希尔排序实现 4.3 希尔排序的特性总结 5、…

面了一个测试工程师要求月薪26K,总感觉他背了很多面试题...

最近有朋友去华为面试&#xff0c;面试前后进行了20天左右&#xff0c;包含4轮电话面试、1轮笔试、1轮主管视频面试、1轮hr视频面试。 据他所说&#xff0c;80%的人都会栽在第一轮面试&#xff0c;要不是他面试前做足准备&#xff0c;估计都坚持不完后面几轮面试。 其实&…

SQL-DDL语句DQL语句

SQL学习笔记 DDL语句--操作数据表 /* 快捷键: insert键 在插入 和 替换模式之间切换 ctrl 字母z 撤销上一步操作 tab 往后缩进(默认4个空格) shift tab 往前缩进(默认4个空格) …

【新星计划回顾】第二篇学习计划-通过定义变量简单批量模拟数据

&#x1f3c6;&#x1f3c6;又到周末&#xff0c;最近这段时间非常忙&#xff0c;虽然导师首次参与新星计划活动已经在4月16日圆满结束&#xff0c;早想腾出时间来好好整理活动期间分享的知识点。 &#x1f3c6;&#x1f3c6;非常感谢大家的支持和活动期间的文章输出&#xff0…