postgresql源码学习(十四)—— 行锁②-update操作与行锁

       本篇以最简单的update操作为例,来看更新过程中的行锁添加、冲突检测、元组状态判断、可见性判断等。

一、 元组的状态类型判断

       HeapTupleSatisfiesUpdate函数(heapam_visibility.c 文件中)根据不同的元组状态,决定继续执行何种操作。

       例如元组是否能被更新取决于是否可见,不可见的元组显然是无需更新的。

状态类型

说明

TM_Ok

元组可见,可以更新

TM_Invisible

元组对当前快照根本不可见,自然无法处理

TM_SelfModified

元组被当前事务更新过

TM_Updated

元组被已提交事务更新过

TM_Deleted

元组被已提交事务删除过

TM_BeingModified

元组正在被其他事务更新

二、 元组更新函数 heap_update()

1. 输入参数

  • relation:待更新的表
  • otid:更新前旧元组的tid(tuple id)
  • newtup:更新后的新元组
  • cid:update命令的id(用于可见性测试,若更新成功则保存在cmax/cmin)
  • crosscheck:if not InvalidSnapshot, also check old tuple against this(没明白)
  • wait:是否需要等待冲突update提交/回滚

2. 输出参数

  • tmfd:更新失败时填充元组的t_ctid,t_xmax,t_cmax
  • lockmode:元组获得的锁模式

函数内容如下(有删减)

TM_Result
heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
			CommandId cid, Snapshot crosscheck, bool wait,
			TM_FailureData *tmfd, LockTupleMode *lockmode)
{
	TM_Result	result;
	TransactionId xid = GetCurrentTransactionId();
/* 开头是一堆变量定义、初始化、判断等,略 */
…
	/* Determine columns modified by the update. 获取待修改的列 */
	modified_attrs = HeapDetermineModifiedColumns(relation, interesting_attrs,
												  &oldtup, newtup);

/* 判断待修改列中是否有主键或唯一键列。如果没有,设置待加锁模式lockmode为for no key update类型 */
	if (!bms_overlap(modified_attrs, key_attrs))
	{
		*lockmode = LockTupleNoKeyExclusive;
		mxact_status = MultiXactStatusNoKeyUpdate;
		key_intact = true;
		MultiXactIdSetOldestMember();
	}
// 如果有,设置待加锁模式lockmode为for update类型
	else
	{
		*lockmode = LockTupleExclusive; 
		mxact_status = MultiXactStatusUpdate;
		key_intact = false;
	}

//某些场景下需要重新判断元组状态并再次执行后续操作,因此这里做了个l2标签,方便跳回
l2:
	checked_lockers = false;
	locker_remains = false;
    //获得当前元组状态(这就是文章开头提到的函数)
	result = HeapTupleSatisfiesUpdate(&oldtup, cid, buffer);

	/* see below about the "no wait" case */
	Assert(result != TM_BeingModified || wait);

    //元组不可见,报错
	if (result == TM_Invisible)
	{
		UnlockReleaseBuffer(buffer);
		ereport(ERROR,
				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
				 errmsg("attempted to update invisible tuple")));
	}
    //元组正在被其他事务更新,并且选择了等待模式
	else if (result == TM_BeingModified && wait)
	{
		TransactionId xwait;
		uint16		infomask;
		bool		can_continue = false;

		//获得当前元组xmax值,但目前还不知道它是事务id还是MultiXactId,要结合infomask判断
		xwait = HeapTupleHeaderGetRawXmax(oldtup.t_data);
		infomask = oldtup.t_data->t_infomask;

//利用infomask判断,如果当前元组的xwait(即xmax)是MultiXactId
		if (infomask & HEAP_XMAX_IS_MULTI)
		{
			TransactionId update_xact;
			int			remain;
			bool		current_is_member = false;

// 判断当前元组MultiXactId中的锁模式,与待加锁模式lockmode是否冲突
// 如果冲突
			if (DoesMultiXactIdConflict((MultiXactId) xwait, infomask,
										*lockmode, ¤t_is_member))
			{
				LockBuffer(buffer, BUFFER_LOCK_UNLOCK);

//待加锁事务是否在MultiXactId对应记录的事务中(是该事务组的成员)
// 如果不是
				if (!current_is_member)
         //获取元组级常规锁,lockmode通过tupleLockExtraInfo映射成常规锁的锁模式
					heap_acquire_tuplock(relation, &(oldtup.t_self), *lockmode,
										 LockWaitBlock, &have_tuple_lock);

//如果是,则可以不用获得锁。直接进入下一步操作,否则可能引起死锁
				//等待冲突事务完成
				MultiXactIdWait((MultiXactId) xwait, mxact_status, infomask,
								relation, &oldtup.t_self, XLTW_Update,
								&remain);
				checked_lockers = true;
				locker_remains = remain != 0;
				LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);

		         //如果元组infomask被修改过,需要重新对元组进行状态判断(goto l2)
				if (xmax_infomask_changed(oldtup.t_data->t_infomask,
										  infomask) ||
					!TransactionIdEquals(HeapTupleHeaderGetRawXmax(oldtup.t_data),
										 xwait))
					goto l2;
			}
	//不符合上层if,即当前元组MultiXactId中的锁模式与待加锁模式lockmode不冲突;或者冲突,但已执行了上面的步骤

             //如果元组被update,delete修改过
			if (!HEAP_XMAX_IS_LOCKED_ONLY(oldtup.t_data->t_infomask))
                 // 获得update,delete的事务id
				update_xact = HeapTupleGetUpdateXid(oldtup.t_data);
			else
                // 否则为invalid事务id
				update_xact = InvalidTransactionId;

//如果元组没有被update,delete修改过,或者有过但已回滚,可以继续操作
if (!TransactionIdIsValid(update_xact) ||
                TransactionIdDidAbort(update_xact))
                can_continue = true;
        }

// 不符合上层大if,即当前元组的xwait(即xmax)不是MultiXactId,而是普通事务id
        else if (TransactionIdIsCurrentTransactionId(xwait))
        {
            //这个锁已经检查过
            checked_lockers = true;
            //虽然xmax是当前事务,也是有锁的
            locker_remains = true;
            //当前事务加的行锁,可以继续操作
            can_continue = true;
        }

       // 如果xmax既不是MultiXactId,又不是当前事务,说明其他事务已对这个元组加锁。如果加的只是key-share锁,并不修改主键或唯一键列,则加锁事务与当前update不冲突,不需要等其结束
        else if (HEAP_XMAX_IS_KEYSHR_LOCKED(infomask) && key_intact)
        {
            //这个锁已经检查过
            checked_lockers = true;
            //元组上有key-share锁
            locker_remains = true;
            //锁不冲突,可以继续操作
            can_continue = true;
        }
// 其他事务已对这个元组加锁,并且锁冲突。下面操作与MultiXactId部分类似。
        else
        {
            LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
            //获得元组常规锁
            heap_acquire_tuplock(relation, &(oldtup.t_self), *lockmode,
                                 LockWaitBlock, &have_tuple_lock);
//等待
            XactLockTableWait(xwait, relation, &oldtup.t_self,
                              XLTW_Update);
            checked_lockers = true;
            LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);

            //如果元组infomask被修改过,需要重新对元组进行状态判断(goto l2)
            if (xmax_infomask_changed(oldtup.t_data->t_infomask, infomask) ||
                !TransactionIdEquals(xwait,
                                     HeapTupleHeaderGetRawXmax(oldtup.t_data)))
                goto l2;

            /* 否则,检查等待的事务是否已提交或回滚 */
            UpdateXmaxHintBits(oldtup.t_data, buffer, xwait);
            if (oldtup.t_data->t_infomask & HEAP_XMAX_INVALID)
                can_continue = true;
        }

//如果可以做update,将元组状态设置为TM_OK	
        if (can_continue)
            result = TM_Ok;
//元组已被update
        else if (!ItemPointerEquals(&oldtup.t_self, &oldtup.t_data->t_ctid))           
            result = TM_Updated;
//元组已被delete
        else
            result = TM_Deleted;
    }
…

/* update操作,为旧元组计算新xmax,可能是单独事务id,也可能是MultiXactId */
    compute_new_xmax_infomask(HeapTupleHeaderGetRawXmax(oldtup.t_data),
                              oldtup.t_data->t_infomask,
                              oldtup.t_data->t_infomask2,
                              xid, *lockmode, true,
                              &xmax_old_tuple, &infomask_old_tuple,
                              &infomask2_old_tuple);

…
}

参考

《PostgreSQL技术内幕:事务处理深度探索》第2章

你可能感兴趣的:(源码学习,PostgreSQL,锁,postgresql,源码,源码学习,update,行锁)