• 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:元组获得的锁模式

    函数内容如下(有删减)

    1. TM_Result
    2. heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
    3. CommandId cid, Snapshot crosscheck, bool wait,
    4. TM_FailureData *tmfd, LockTupleMode *lockmode)
    5. {
    6. TM_Result result;
    7. TransactionId xid = GetCurrentTransactionId();
    8. /* 开头是一堆变量定义、初始化、判断等,略 */
    9. /* Determine columns modified by the update. 获取待修改的列 */
    10. modified_attrs = HeapDetermineModifiedColumns(relation, interesting_attrs,
    11. &oldtup, newtup);
    12. /* 判断待修改列中是否有主键或唯一键列。如果没有,设置待加锁模式lockmode为for no key update类型 */
    13. if (!bms_overlap(modified_attrs, key_attrs))
    14. {
    15. *lockmode = LockTupleNoKeyExclusive;
    16. mxact_status = MultiXactStatusNoKeyUpdate;
    17. key_intact = true;
    18. MultiXactIdSetOldestMember();
    19. }
    20. // 如果有,设置待加锁模式lockmode为for update类型
    21. else
    22. {
    23. *lockmode = LockTupleExclusive;
    24. mxact_status = MultiXactStatusUpdate;
    25. key_intact = false;
    26. }
    27. //某些场景下需要重新判断元组状态并再次执行后续操作,因此这里做了个l2标签,方便跳回
    28. l2:
    29. checked_lockers = false;
    30. locker_remains = false;
    31. //获得当前元组状态(这就是文章开头提到的函数)
    32. result = HeapTupleSatisfiesUpdate(&oldtup, cid, buffer);
    33. /* see below about the "no wait" case */
    34. Assert(result != TM_BeingModified || wait);
    35. //元组不可见,报错
    36. if (result == TM_Invisible)
    37. {
    38. UnlockReleaseBuffer(buffer);
    39. ereport(ERROR,
    40. (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
    41. errmsg("attempted to update invisible tuple")));
    42. }
    43. //元组正在被其他事务更新,并且选择了等待模式
    44. else if (result == TM_BeingModified && wait)
    45. {
    46. TransactionId xwait;
    47. uint16 infomask;
    48. bool can_continue = false;
    49. //获得当前元组xmax值,但目前还不知道它是事务id还是MultiXactId,要结合infomask判断
    50. xwait = HeapTupleHeaderGetRawXmax(oldtup.t_data);
    51. infomask = oldtup.t_data->t_infomask;
    52. //利用infomask判断,如果当前元组的xwait(即xmax)是MultiXactId
    53. if (infomask & HEAP_XMAX_IS_MULTI)
    54. {
    55. TransactionId update_xact;
    56. int remain;
    57. bool current_is_member = false;
    58. // 判断当前元组MultiXactId中的锁模式,与待加锁模式lockmode是否冲突
    59. // 如果冲突
    60. if (DoesMultiXactIdConflict((MultiXactId) xwait, infomask,
    61. *lockmode, ¤t_is_member))
    62. {
    63. LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
    64. //待加锁事务是否在MultiXactId对应记录的事务中(是该事务组的成员)
    65. // 如果不是
    66. if (!current_is_member)
    67. //获取元组级常规锁,lockmode通过tupleLockExtraInfo映射成常规锁的锁模式
    68. heap_acquire_tuplock(relation, &(oldtup.t_self), *lockmode,
    69. LockWaitBlock, &have_tuple_lock);
    70. //如果是,则可以不用获得锁。直接进入下一步操作,否则可能引起死锁
    71. //等待冲突事务完成
    72. MultiXactIdWait((MultiXactId) xwait, mxact_status, infomask,
    73. relation, &oldtup.t_self, XLTW_Update,
    74. &remain);
    75. checked_lockers = true;
    76. locker_remains = remain != 0;
    77. LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
    78. //如果元组infomask被修改过,需要重新对元组进行状态判断(goto l2)
    79. if (xmax_infomask_changed(oldtup.t_data->t_infomask,
    80. infomask) ||
    81. !TransactionIdEquals(HeapTupleHeaderGetRawXmax(oldtup.t_data),
    82. xwait))
    83. goto l2;
    84. }
    85. //不符合上层if,即当前元组MultiXactId中的锁模式与待加锁模式lockmode不冲突;或者冲突,但已执行了上面的步骤
    86. //如果元组被update,delete修改过
    87. if (!HEAP_XMAX_IS_LOCKED_ONLY(oldtup.t_data->t_infomask))
    88. // 获得update,delete的事务id
    89. update_xact = HeapTupleGetUpdateXid(oldtup.t_data);
    90. else
    91. // 否则为invalid事务id
    92. update_xact = InvalidTransactionId;
    93. //如果元组没有被update,delete修改过,或者有过但已回滚,可以继续操作
    94. if (!TransactionIdIsValid(update_xact) ||
    95. TransactionIdDidAbort(update_xact))
    96. can_continue = true;
    97. }
    98. // 不符合上层大if,即当前元组的xwait(即xmax)不是MultiXactId,而是普通事务id
    99. else if (TransactionIdIsCurrentTransactionId(xwait))
    100. {
    101. //这个锁已经检查过
    102. checked_lockers = true;
    103. //虽然xmax是当前事务,也是有锁的
    104. locker_remains = true;
    105. //当前事务加的行锁,可以继续操作
    106. can_continue = true;
    107. }
    108. // 如果xmax既不是MultiXactId,又不是当前事务,说明其他事务已对这个元组加锁。如果加的只是key-share锁,并不修改主键或唯一键列,则加锁事务与当前update不冲突,不需要等其结束
    109. else if (HEAP_XMAX_IS_KEYSHR_LOCKED(infomask) && key_intact)
    110. {
    111. //这个锁已经检查过
    112. checked_lockers = true;
    113. //元组上有key-share锁
    114. locker_remains = true;
    115. //锁不冲突,可以继续操作
    116. can_continue = true;
    117. }
    118. // 其他事务已对这个元组加锁,并且锁冲突。下面操作与MultiXactId部分类似。
    119. else
    120. {
    121. LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
    122. //获得元组常规锁
    123. heap_acquire_tuplock(relation, &(oldtup.t_self), *lockmode,
    124. LockWaitBlock, &have_tuple_lock);
    125. //等待
    126. XactLockTableWait(xwait, relation, &oldtup.t_self,
    127. XLTW_Update);
    128. checked_lockers = true;
    129. LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
    130. //如果元组infomask被修改过,需要重新对元组进行状态判断(goto l2)
    131. if (xmax_infomask_changed(oldtup.t_data->t_infomask, infomask) ||
    132. !TransactionIdEquals(xwait,
    133. HeapTupleHeaderGetRawXmax(oldtup.t_data)))
    134. goto l2;
    135. /* 否则,检查等待的事务是否已提交或回滚 */
    136. UpdateXmaxHintBits(oldtup.t_data, buffer, xwait);
    137. if (oldtup.t_data->t_infomask & HEAP_XMAX_INVALID)
    138. can_continue = true;
    139. }
    140. //如果可以做update,将元组状态设置为TM_OK
    141. if (can_continue)
    142. result = TM_Ok;
    143. //元组已被update
    144. else if (!ItemPointerEquals(&oldtup.t_self, &oldtup.t_data->t_ctid))
    145. result = TM_Updated;
    146. //元组已被delete
    147. else
    148. result = TM_Deleted;
    149. }
    150. /* update操作,为旧元组计算新xmax,可能是单独事务id,也可能是MultiXactId */
    151. compute_new_xmax_infomask(HeapTupleHeaderGetRawXmax(oldtup.t_data),
    152. oldtup.t_data->t_infomask,
    153. oldtup.t_data->t_infomask2,
    154. xid, *lockmode, true,
    155. &xmax_old_tuple, &infomask_old_tuple,
    156. &infomask2_old_tuple);
    157. }

    参考

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

  • 相关阅读:
    Javase | StringBuffer、StringBuilder
    Python 列表推导式:计算不同运动组合的热量列表及最大最小热量消耗情况
    电子电路设计基本概念100问(六)【学习目标:原理图、PCB、阻抗设计、电子设计基本原则、基本原器件等】
    Java之HashMap经典算法-红黑树(插入节点平衡调整,左旋转,右旋转)
    从0到1 手把手搭建spring cloud alibaba 微服务大型应用框架(九)文件服务篇(1):minio 单机与集群搭建
    #pragma once 2023/9/24 上午11:39:05
    挑战10个最难回答的Java面试题(附答案)
    【论文笔记】基于深度强化学习的室内视觉局部路径规划
    【Python、Qt】使用QItemDelegate实现单元格的富文本显示+复选框功能
    开源语言大模型演进史:向LLaMA 2看齐
  • 原文地址:https://blog.csdn.net/Hehuyi_In/article/details/126908355