海山数据库(He3DB)源码详解:MVCC可见性HeapTupleSatisfiesUpdate函数

27 阅读9分钟

海山数据库(He3DB)源码详解:MVCC可见性HeapTupleSatisfiesUpdate函数

本文介绍了事务执行过程中,检查一个Update元组是否对当前事务可见,用于确保事务可以看到正确的数据版本

HeapTupleSatisfiesUpdate函数源码解读

HeapTupleSatisfiesUpdate用于确定一个堆元组在更新操作中是否满足当前事务的可见性及其状态;

函数声明
TM_Result
HeapTupleSatisfiesUpdate(HeapTuple htup, CommandId curcid,
						 Buffer buffer)

参数说明:

  • HeapTuple htup:指向堆元组指针;
  • CommandId curcid:当前事务的命令ID;
  • Buffer buffer:包含堆元组的缓冲区。w

返回值说明:

  • TM_Ok:元组有效且可见,因此可以被更新;
  • TM_Invisible:当扫描开始时,元组根本不存在;
  • TM_SelfModified:元组在当前事务中被更新,且更新发生在当前扫描开始之后;
  • TM_Updated:元组被已提交的事务更新了(包括元组被移动到不同分区的情况。);
  • TM_Deleted:元组被已提交的事务删除了;
  • TM_BeingModified:元组正在被当前事务之外的进行中的事务更新(包括元组被MultiXact共享锁定的情况,即使MultiXact包括当前事务。)
函数说明:

HeapTupleSatisfiesUpdate函数用于确定一个堆元组在更新操作中是否可见及其状态,并返回TM_Result类型的的值,表示元组的不同状态。该函数的流程图如下:

在这里插入图片描述

函数执行过程
  • 获取元组头,并确保元组的自指针和元组的表OID有效
	HeapTupleHeader tuple = htup->t_data;

	Assert(ItemPointerIsValid(&htup->t_self));
	Assert(htup->t_tableOid != InvalidOid);
首先,判断Xmin是否提交,如果未提交,执行以下判断:
	if (!HeapTupleHeaderXminCommitted(tuple))
	{
		/* 1.检查元组的Xmin有效性 */
		/* 2.检查元组是否被标记为HEAP_MOVED_OFF */
		/* 3.检查元组是否被标记为HEAP_MOVED_IN  */
		/* 4.检查Xmin事务ID是否为当前事务  */
		/* 5.检查Xmin事务ID是否为在运行中  */
		/* 6.检查Xmin事务ID是否为已经提交  */
		/* 7.其他(事务中止或系统崩溃) */
	}
  1. 如果元组的Xmin事务已经无效,返回TM_Invisible;
	if (HeapTupleHeaderXminInvalid(tuple))
		return TM_Invisible;
  1. 检查元组是否被标记为HEAP_MOVED_OFF,获取与元组相关的xavc事务ID:
  • 事务IDxvac为当前事务ID,返回TM_Invisible;
  • 事务IDxvac不在进行中且已经提交,则设置Xmin的提示位为HEAP_XMIN_INVALID,返回TM_Invisible;
  • 事务IDxvac不在进行中且没有提交,则设置Xmin的提示位为HEAP_XMIN_COMMITTED。
	/* Used by pre-9.0 binary upgrades */
	if (tuple->t_infomask & HEAP_MOVED_OFF)
	{
		TransactionId xvac = HeapTupleHeaderGetXvac(tuple);

		if (TransactionIdIsCurrentTransactionId(xvac))
			return TM_Invisible;
		if (!TransactionIdIsInProgress(xvac))
		{
			if (TransactionIdDidCommit(xvac))
			{
				SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
							InvalidTransactionId);
				return TM_Invisible;
			}
			SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
						InvalidTransactionId);
		}
	}
  1. 检查元组是否被标记为HEAP_MOVED_IN,获取与元组相关的xavc事务ID:
  • 如果事务IDxvac不是当前事务,且在后台运行中,返回TM_Invisible;
  • 如果事务IDxvac不是当前事务,且已经提交,则设置Xmin的提示位为HEAP_XMIN_COMMITTED;
  • 如果事务IDxvac不是当前事务,且没有提交,则设置Xmin的提示位为HEAP_XMIN_INVALID,返回TM_Invisible。
	else if (tuple->t_infomask & HEAP_MOVED_IN)
	{
		TransactionId xvac = HeapTupleHeaderGetXvac(tuple);

		if (!TransactionIdIsCurrentTransactionId(xvac))
		{
			if (TransactionIdIsInProgress(xvac))
				return TM_Invisible;
			if (TransactionIdDidCommit(xvac))
				SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
							InvalidTransactionId);
			else
			{
				SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
							InvalidTransactionId);
				return TM_Invisible;
			}
		}
	}
  1. 检查Xmin是否时当前事务ID:
	else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tuple)))
	{
		/* 1. 检查元组的Xmax是否大于当前命令ID */
		/* 2. 检查元组的Xmax是否无效 */
		/* 3. 检查元组是否仅被锁定 */
		/* 4. 检查元组的Xmax是否是组合事务 */
		/* 5. 检查元组的Xmax是否当前事务ID */
		/* 6. 检查元组的cmax是否大于当前命令ID */
	}
  • 如果元组的**cmin(创建命令ID)**大于当前命令ID,返回TM_Invisible,表示元组在扫描后被插入;
	if (HeapTupleHeaderGetCmin(tuple) >= curcid)
		return TM_Invisible;	/* inserted after scan started */
  • 如果元组的Xmax无效,返回TM_OK;
	if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid */
		return TM_Ok;
  • 检查元组的Xmax是否仅被锁定,取得Xmax事务ID:
    • 如果元组的Xmax是组合事务且有事务正在进行中,返回TM_BeingModified;
    • 如果元组的Xmax是组合事务但没有事务正在进行,返回TM_Ok;
    • 如果元组的Xmax没有在运行中(已完成)返回TM_Ok;
    • 其他元组的Xmax事务正在进行中,返回TM_BeingModified;
	if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
	{
		TransactionId xmax;

		xmax = HeapTupleHeaderGetRawXmax(tuple);

		/*
		 * Careful here: even though this tuple was created by our own
		 * transaction, it might be locked by other transactions, if
		 * the original version was key-share locked when we updated
		 * it.
		 */

		if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
		{
			if (MultiXactIdIsRunning(xmax, true))
				return TM_BeingModified;
			else
				return TM_Ok;
		}

		/*
		 * If the locker is gone, then there is nothing of interest
		 * left in this Xmax; otherwise, report the tuple as
		 * locked/updated.
		 */
		if (!TransactionIdIsInProgress(xmax))
			return TM_Ok;
		return TM_BeingModified;
	}
  • 如果元组的Xmax的事务是组合事务,取得Xmax事务ID并判断Xmax事务是否有效:
    • 如果Xmax事务不是当前事务ID,且事务在运行中,返回TM_BeingModified;
    • 如果Xmax事务不是当前事务ID,但事务没有运行,返回TM_Ok;
    • 如果Xmax事务是当前事务ID,且cmax(更新命令ID)≥ curcid,返回TM_SelfModified;
    • 如果Xmax事务是当前事务ID,且cmax(更新命令ID)< curcid,返回TM_Invisible;
	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));

		/* deleting subtransaction must have aborted */
		if (!TransactionIdIsCurrentTransactionId(xmax))
		{
			if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple),
										false))
				return TM_BeingModified;
			return TM_Ok;
		}
		else
		{
			if (HeapTupleHeaderGetCmax(tuple) >= curcid)
				return TM_SelfModified; /* updated after scan started */
			else
				return TM_Invisible;	/* updated before scan started */
		}
	}
  • 如果元组的Xmax的事务是当前事务ID,设置标志位为HEAP_XMAX_INVALID,返回TM_Ok;
	if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
	{
		/* deleting subtransaction must have aborted */
		SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
					InvalidTransactionId);
		return TM_Ok;
	}
  • 如果元组的Cmax ≥ curcid(当前命令ID),返回TM_SelfModified;
  • 如果元组的Cmax ≥ curcid(当前命令ID),返回TM_Invisible;
	if (HeapTupleHeaderGetCmax(tuple) >= curcid)
		return TM_SelfModified; /* updated after scan started */
	else
		return TM_Invisible;	/* updated before scan started */
  1. 检查Xmin是否在运行,如果在运行返回TM_Invisible;
  2. 检查Xmin是否已提交,如果已提交设置标志位为HEAP_XMIN_COMMITTED;
  3. 否则,Xmin已经中止或者崩溃,设置标志位为HEAP_XMIN_INVALID,返回TM_Invisible;
	else if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmin(tuple)))
		return TM_Invisible;
	else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple)))
		SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
					HeapTupleHeaderGetRawXmin(tuple));
	else
	{
		/* it must have aborted or crashed */
		SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
					InvalidTransactionId);
		return TM_Invisible;
	}
之后,判断Xmax的状态:
  1. 检查Xmax的事务是否无效,如果已经无效,返回TM_Ok;
	if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid or aborted */
		return TM_Ok;
  1. 检查Xmax的事务是否提交,
  • 如果事务已提交且仅被锁定,返回TM_Ok;
  • 如果事务已提交且元组的当前位置和ctid相等,则返回TM_Updated;
  • 如果事务已提交且元组的当前位置和ctid不等,则返回TM_Deleted;
	if (tuple->t_infomask & HEAP_XMAX_COMMITTED)
	{
		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
			return TM_Ok;
		if (!ItemPointerEquals(&htup->t_self, &tuple->t_ctid))
			return TM_Updated;	/* updated by other */
		else
			return TM_Deleted;	/* deleted by other */
	}
  1. 检查Xmax事务是否为组合事务,并获得Xmax事务ID:
	if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
	{
		TransactionId xmax;
		/* 1. 检查Xmax事务是否被锁定更新 */
		/* 2. 检查Xmax事务是否仅被锁定 */
		/* 3. 获取Xmax事务ID并检查其有效性 */
		/* 4. 检查Xmax事务是否是当前事务 */
		/* 5. 检查Xmax事务是否在进行中 */
		/* 6. 检查Xmax事务是否已经提交 */
		/* 7. 检查Xmax事务是否组合事务在运行中 */
	}
  • 如果Xmax被锁定升级,返回TM_OK;
	if (HEAP_LOCKED_UPGRADED(tuple->t_infomask))
		return TM_Ok;
  • 如果Xmax仅被锁定且事务在进行中,返回TM_BeingModified;
  • 如果Xmax仅被锁定且事务不在进行,设置标志位为HEAP_XMAX_INVALID,并返回TM_OK;
	if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
	{
		if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), true))
			return TM_BeingModified;

		SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId);
		return TM_Ok;
	}
  • 获取更新操作的Xmax事务ID,并检查其有效性,如果无效且事务在进行中,返回TM_BeingModified;
	xmax = HeapTupleGetUpdateXid(tuple);
	if (!TransactionIdIsValid(xmax))
	{
		if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), false))
			return TM_BeingModified;
	}
  • 如果Xmax是当前事务ID,且cmax ≥ curcid返回TM_SelfModified;
  • 如果Xmax是当前事务ID,且cmax < curcid返回TM_Invisible;
	/* not LOCKED_ONLY, so it has to have an xmax */
	Assert(TransactionIdIsValid(xmax));

	if (TransactionIdIsCurrentTransactionId(xmax))
	{
		if (HeapTupleHeaderGetCmax(tuple) >= curcid)
			return TM_SelfModified; /* updated after scan started */
		else
			return TM_Invisible;	/* updated before scan started */
	}
  • 如果Xmax事务正在进行中,返回TM_BeingModified;
	if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), false))
		return TM_BeingModified;
  • 如果Xmax事务已提交,比较当前位置的tid和ctid相同,返回TM_Updated;
  • 如果Xmax事务已提交,比较当前位置的tid和ctid不同,返回TM_Deleted;
	if (TransactionIdDidCommit(xmax))
	{
		if (!ItemPointerEquals(&htup->t_self, &tuple->t_ctid))
			return TM_Updated;
		else
			return TM_Deleted;
	}
  • 如果Xmax事务不在组合事务中运行,设置标志位为HEAP_XMAX_INVALID,返回TM_Ok;
  • 如果Xmax事务在组合事务中运行,返回TM_BeingModified;
	/*
	 * By here, the update in the Xmax is either aborted or crashed, but
	 * what about the other members?
	 */

	if (!MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), false))
	{
		/*
			* There's no member, even just a locker, alive anymore, so we can
			* mark the Xmax as invalid.
			*/
		SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
					InvalidTransactionId);
		return TM_Ok;
	}
	else
	{
		/* There are lockers running */
		return TM_BeingModified;
	}
  1. 检查Xmax事务是否是当前事务ID,如果是当前事务ID:
  • 如果是仅被锁定,返回TM_BeingModified;
  • 如果当前cid ≥ curcid,返回TM_SelfModified,否则返回TM_Invisible;
	if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
	{
		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
			return TM_BeingModified;
		if (HeapTupleHeaderGetCmax(tuple) >= curcid)
			return TM_SelfModified; /* updated after scan started */
		else
			return TM_Invisible;	/* updated before scan started */
	}
  1. 检查Xmax事务正在进行中,如果正在进行中,返回TM_BeingModified;
	if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(tuple)))
		return TM_BeingModified;
  1. 检查Xmax事务是否提交,如果未提交则设置标志位为HEAP_XMAX_INVALID,并返回TM_Ok;
	if (!TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple)))
	{
		/* it must have aborted or crashed */
		SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
					InvalidTransactionId);
		return TM_Ok;
	}
  1. 检查Xmax事务是否仅被锁定,如果锁定则设置标志位为HEAP_XMAX_INVALID,并返回TM_Ok;
	if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
	{
		SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
					InvalidTransactionId);
		return TM_Ok;
	}
  1. 设置Xmax事务标志位为HEAP_XMAX_COMMITTED,并检查当前ctid和ctid,
  • 如果不相等,则返回TM_Updated;
  • 如果相等,则返回TM_Deleted;
	SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED,
				HeapTupleHeaderGetRawXmax(tuple));
	if (!ItemPointerEquals(&htup->t_self, &tuple->t_ctid))
		return TM_Updated;		/* updated by other */
	else
		return TM_Deleted;		/* deleted by other */

该函数通过检查XminXmax事务的状态(是否已提交、是否进行中、是否是当前事务等),来确定元组对更新是否有效。

AX_COMMITTED,并检查当前ctid和ctid,

  • 如果不相等,则返回TM_Updated;
  • 如果相等,则返回TM_Deleted;
	SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED,
				HeapTupleHeaderGetRawXmax(tuple));
	if (!ItemPointerEquals(&htup->t_self, &tuple->t_ctid))
		return TM_Updated;		/* updated by other */
	else
		return TM_Deleted;		/* deleted by other */

该函数通过检查XminXmax事务的状态(是否已提交、是否进行中、是否是当前事务等),来确定元组对更新是否有效。