本篇以最简单的update操作为例,来看更新过程中的行锁添加、冲突检测、元组状态判断、可见性判断等。
HeapTupleSatisfiesUpdate函数(heapam_visibility.c 文件中)根据不同的元组状态,决定继续执行何种操作。
例如元组是否能被更新取决于是否可见,不可见的元组显然是无需更新的。
状态类型 |
说明 |
TM_Ok |
元组可见,可以更新 |
TM_Invisible |
元组对当前快照根本不可见,自然无法处理 |
TM_SelfModified |
元组被当前事务更新过 |
TM_Updated |
元组被已提交事务更新过 |
TM_Deleted |
元组被已提交事务删除过 |
TM_BeingModified |
元组正在被其他事务更新 |
函数内容如下(有删减)
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章