本篇以最简单的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章