MVCC(4)--事务可见性

引言

在上一篇博客MVCC(3)–事务可见性中,我们分析了判断数据有效性、可见性、可更新性的算法的实现两个主要函数中的 本篇博客将会分析两大主要函数中的另一个—HeapTupleSatisfiesMVCC 文件路径:src\gausskernel\storage\access\heap\heapam_visibility.cpp

HeapTupleSatisfiesMVCC

一、简化的HeapTupleSatisfiesMVCC流程图

HeapTupleSatisfiesMVCC`的函数,它用于检查一个堆元组(Heap Tuple)是否满足多版本并发控制(MVCC)的条件。这是数据库事务处理中的一种常见机制,用于处理并发读写操作。

函数接收三个参数:

  • htup(一个堆元组),
  • snapshot(一个快照,通常包含了某一时刻的数据库状态),
  • 以及buffer(一个缓冲区,通常用于存储临时数据或者是待处理的数据)。 其处理过程大致分为如下图所示的三个模块

如上图

  • 函数首先进行一些基本的断言检查,以确保输入的堆元组是有效的。
  • 然后,它会根据不同的条件检查元组是否对当前的事务可见。这包括检查元组是否被当前- 事务修改,是否被其他事务修改,以及这些事务的状态(是否已提交或中止)。
  • 如果元组满足所有条件并对当前事务可见,则函数返回true;否则,返回false。 下面我们将对事务可见性检查的内容做重点解读。

二、事务可见性部分详细分析

事务可见性判断代码块中保证了数据一致性和隔离性,我们对其的重要步骤做逐步分析。 这些步骤大体上是对元组进行一系列复杂的并发控制和事务隔离检查,并根据检查结果进行相应的处理。

调用HeapTupleHeaderXminCommitted(tuple))判断是否元组的xmin还没有提交。
  • 调用HeapTupleHeaderXminInvalid(tuple)判断元组的有效性

  • 判断是否当前的快照不是一个多版本并发控制(MVCC)的快照,并且元组的xmin是当前事务的ID。

  • 对元组状态的检查和处理。

XidVisibleInSnapshot函数检查元组的xmin在给定的快照中是否可见。然后根据这个函数的返回结果和一些其他条件,可能会返回false,或者设置一些提示位。

简化后的事务可见性模块流程图如下所示

由于上图对红、橙、黄三个矩形所代表的模块进行了简化表达,接下来我们对这三个方框的内容进行补充说明。

元组检查

例如,检查元组是否在扫描开始后被插入或删除,检查元组的xmax是否是一个多事务ID,检查元组的xmax是否是当前事务的ID等等。根据这些检查的结果,可能会返回true或false,或者设置一些提示位。

  • 判断元组的xmax是否有效

  • 判断是否元组只被锁定,而没有被删除。

  • 元组是否为被多事务共享

  • 元组的信息掩码与HEAP_XMAX_COMMITTED进行位与运算的结果为0。如果满足对元组进行一些更复杂的检查和处理。

  • 元组的xmax在给定的快照中是否可见。

快照可见检查
  • 元组的xmin(也就是该元组被插入或更新的事务ID)在给定的快照中是否可见,并将结果存储在visible变量中。返回一个提示状态,存储在hintstatus变量中。

  • 如果提示状态表示xmin对应的事务已经提交,那么就设置元组的提示位为HEAP_XMIN_COMMITTED

  • 如果提示状态表示xmin对应的事务已经中止,那么进行一些更复杂的检查和处理。

  • 如果元组的xmin在给定的快照中不可见,那么进入下面的代码块,在这个代码块中,会进行一些更复杂的检查和处理。

recheck_xmax检查大致同上,此处不再赘述

三、源码以及完整注释

文件路径:src\gausskernel\storage\access\heap\heapam_visibility.cpp
 
  1. static bool HeapTupleSatisfiesMVCC(HeapTuple htup, Snapshot snapshot, Buffer buffer)
  2. {
  3. HeapTupleHeader tuple = htup->t_data;
  4. Assert(ItemPointerIsValid(&htup->t_self));
  5. Assert(htup->t_tableOid != InvalidOid);
  6. bool visible = false;
  7. TransactionIdStatus hintstatus;
  8. Page page = BufferGetPage(buffer);
  9. ereport(DEBUG1,
  10. (errmsg("HeapTupleSatisfiesMVCC self(%d,%d) ctid(%d,%d) cur_xid %ld xmin %ld"
  11. " xmax %ld csn %lu",
  12. ItemPointerGetBlockNumber(&htup->t_self),
  13. ItemPointerGetOffsetNumber(&htup->t_self),
  14. ItemPointerGetBlockNumber(&tuple->t_ctid),
  15. ItemPointerGetOffsetNumber(&tuple->t_ctid),
  16. GetCurrentTransactionIdIfAny(),
  17. HeapTupleHeaderGetXmin(page, tuple),
  18. HeapTupleHeaderGetXmax(page, tuple),
  19. snapshot->snapshotcsn)));
  20. /*
  21. * Just valid for read-only transaction when u_sess->attr.attr_common.XactReadOnly is true.
  22. * Show any tuples including dirty ones when u_sess->attr.attr_storage.enable_show_any_tuples is true.
  23. * GUC param u_sess->attr.attr_storage.enable_show_any_tuples is just for analyse or maintenance
  24. */
  25. if (u_sess->attr.attr_common.XactReadOnly && u_sess->attr.attr_storage.enable_show_any_tuples)
  26. return true;
  27. if (!HeapTupleHeaderXminCommitted(tuple)) {
  28. if (HeapTupleHeaderXminInvalid(tuple))
  29. return false;
  30. /* IMPORTANT: Version snapshot is independent of the current transaction. */
  31. if (!IsVersionMVCCSnapshot(snapshot) &&
  32. TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(page, tuple))) {
  33. if ((tuple->t_infomask & HEAP_COMBOCID) && CheckStreamCombocid(tuple, snapshot->curcid, page))
  34. return true; /* delete after stream producer thread scan started */
  35. if (HeapTupleHeaderGetCmin(tuple, page) >= snapshot->curcid)
  36. return false; /* inserted after scan started */
  37. if (tuple->t_infomask & HEAP_XMAX_INVALID) /* xid invalid */
  38. return true;
  39. if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask, tuple->t_infomask2)) /* not deleter */
  40. return true;
  41. if (tuple->t_infomask & HEAP_XMAX_IS_MULTI) {
  42. TransactionId xmax = HeapTupleHeaderMultiXactGetUpdateXid(page, tuple);
  43. /* not LOCKED_ONLY, so it has to have an xmax */
  44. Assert(TransactionIdIsValid(xmax));
  45. /* updating subtransaction must have aborted */
  46. if (!TransactionIdIsCurrentTransactionId(xmax))
  47. return true;
  48. else if (HeapTupleHeaderGetCmax(tuple, page) >= snapshot->curcid)
  49. return true; /* updated after scan started */
  50. else
  51. return false; /* updated before scan started */
  52. }
  53. if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmax(page, tuple))) {
  54. /* deleting subtransaction must have aborted */
  55. Assert(!TransactionIdDidCommit(HeapTupleHeaderGetXmax(page, tuple)));
  56. SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId);
  57. return true;
  58. }
  59. if (HeapTupleHeaderGetCmax(tuple, page) >= snapshot->curcid)
  60. return true; /* deleted after scan started */
  61. else
  62. return false; /* deleted before scan started */
  63. } else {
  64. visible = XidVisibleInSnapshot(HeapTupleHeaderGetXmin(page, tuple), snapshot, &hintstatus, buffer, NULL);
  65. if (hintstatus == XID_COMMITTED)
  66. SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED, HeapTupleHeaderGetXmin(page, tuple));
  67. if (hintstatus == XID_ABORTED) {
  68. if (!LatestFetchCSNDidAbort(HeapTupleHeaderGetXmin(page, tuple)))
  69. LatestTransactionStatusError(HeapTupleHeaderGetXmin(page, tuple),
  70. snapshot,
  71. "HeapTupleSatisfiesMVCC set HEAP_XMIN_INVALID xid don't abort");
  72. SetHintBits(tuple, buffer, HEAP_XMIN_INVALID, InvalidTransactionId);
  73. }
  74. if (!visible) {
  75. if (!GTM_LITE_MODE || u_sess->attr.attr_common.xc_maintenance_mode ||
  76. snapshot->gtm_snapshot_type != GTM_SNAPSHOT_TYPE_LOCAL ||
  77. !IsXidVisibleInGtmLiteLocalSnapshot(HeapTupleHeaderGetXmin(page, tuple), snapshot, hintstatus,
  78. HeapTupleHeaderGetXmin(page, tuple) == HeapTupleHeaderGetXmax(page, tuple), buffer, NULL)) {
  79. return false;
  80. }
  81. }
  82. }
  83. } else {
  84. /* xmin is committed, but maybe not according to our snapshot */
  85. if (!HeapTupleHeaderXminFrozen(tuple) &&
  86. !CommittedXidVisibleInSnapshot(HeapTupleHeaderGetXmin(page, tuple), snapshot, buffer)) {
  87. /* tuple xmin has already committed, no need to use xc_maintenance_mod bypass */
  88. if (!GTM_LITE_MODE || snapshot->gtm_snapshot_type != GTM_SNAPSHOT_TYPE_LOCAL ||
  89. !IsXidVisibleInGtmLiteLocalSnapshot(HeapTupleHeaderGetXmin(page, tuple), snapshot, XID_COMMITTED,
  90. HeapTupleHeaderGetXmin(page, tuple) == HeapTupleHeaderGetXmax(page, tuple), buffer, NULL)) {
  91. return false; /* treat as still in progress */
  92. }
  93. }
  94. }
  95. recheck_xmax:
  96. if (tuple->t_infomask & HEAP_XMAX_INVALID) /* xid invalid or aborted */
  97. return true;
  98. if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask, tuple->t_infomask2))
  99. return true;
  100. if (tuple->t_infomask & HEAP_XMAX_IS_MULTI) {
  101. TransactionId xmax = HeapTupleHeaderMultiXactGetUpdateXid(page, tuple);
  102. /* not LOCKED_ONLY, so it has to have an xmax */
  103. Assert(TransactionIdIsValid(xmax));
  104. if (TransactionIdIsCurrentTransactionId(xmax)) {
  105. if (HeapTupleHeaderGetCmax(tuple, page) >= snapshot->curcid)
  106. return true; /* deleted after scan started */
  107. else
  108. return false; /* deleted before scan started */
  109. }
  110. if (TransactionIdIsInProgress(xmax))
  111. return true;
  112. if (TransactionIdDidCommit(xmax)) {
  113. /* updating transaction committed, but when? */
  114. if (!CommittedXidVisibleInSnapshot(xmax, snapshot, buffer))
  115. return true; /* treat as still in progress */
  116. return false;
  117. }
  118. /* it must have aborted or crashed */
  119. return true;
  120. }
  121. if (!(tuple->t_infomask & HEAP_XMAX_COMMITTED)) {
  122. bool sync = false;
  123. TransactionId xmax = HeapTupleHeaderGetXmax(page, tuple);
  124. /* IMPORTANT: Version snapshot is independent of the current transaction. */
  125. if (!IsVersionMVCCSnapshot(snapshot) &&
  126. TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmax(page, tuple))) {
  127. if (HeapTupleHeaderGetCmax(tuple, page) >= snapshot->curcid)
  128. return true; /* deleted after scan started */
  129. else
  130. return false; /* deleted before scan started */
  131. }
  132. visible = XidVisibleInSnapshot(HeapTupleHeaderGetXmax(page, tuple), snapshot, &hintstatus, buffer, &sync);
  133. /*
  134. * If sync wait, xmax may be modified by others. So we need to check xmax again after acquiring the page lock.
  135. */
  136. if (sync && (xmax != HeapTupleHeaderGetXmax(page, tuple))) {
  137. goto recheck_xmax;
  138. }
  139. if (hintstatus == XID_COMMITTED) {
  140. /* xmax transaction committed */
  141. SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED, HeapTupleHeaderGetXmax(page, tuple));
  142. }
  143. if (hintstatus == XID_ABORTED) {
  144. if (!LatestFetchCSNDidAbort(HeapTupleHeaderGetXmax(page, tuple)))
  145. LatestTransactionStatusError(HeapTupleHeaderGetXmax(page, tuple),
  146. snapshot,
  147. "HeapTupleSatisfiesMVCC set HEAP_XMAX_INVALID xid don't abort");
  148. /* it must have aborted or crashed */
  149. SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId);
  150. }
  151. if (!visible) {
  152. if (!GTM_LITE_MODE || u_sess->attr.attr_common.xc_maintenance_mode ||
  153. snapshot->gtm_snapshot_type != GTM_SNAPSHOT_TYPE_LOCAL ||
  154. !IsXidVisibleInGtmLiteLocalSnapshot(HeapTupleHeaderGetXmax(page, tuple),
  155. snapshot, hintstatus, false, buffer, &sync)) {
  156. if (sync && (xmax != HeapTupleHeaderGetXmax(page, tuple))) {
  157. goto recheck_xmax;
  158. }
  159. return true; /* treat as still in progress */
  160. }
  161. }
  162. } else {
  163. /* xmax is committed, but maybe not according to our snapshot */
  164. if (!CommittedXidVisibleInSnapshot(HeapTupleHeaderGetXmax(page, tuple), snapshot, buffer)) {
  165. if (!GTM_LITE_MODE || snapshot->gtm_snapshot_type != GTM_SNAPSHOT_TYPE_LOCAL ||
  166. !IsXidVisibleInGtmLiteLocalSnapshot(HeapTupleHeaderGetXmax(page, tuple),
  167. snapshot, XID_COMMITTED, false, buffer, NULL)) {
  168. return true; /* treat as still in progress */
  169. }
  170. }
  171. }
  172. return false;
  173. }

四、小结

本次博客对HeapTupleSatisfiesMVCC函数做了详细分析,其用于一般读事务的快照扫描。 对于其实现细节做了详尽分析

下一篇博客我们将关注实现MVCC可见性的其他重点函数HeapTupleSatisfiesVacuum

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
PostgreSQL 是一个功能强大的关系型数据库管理系统,在处理数据库事务和并发控制方面有着很多特和机制。下面是关于 PostgreSQL 数据库事务和并发控制的一些重要信息: 1. 数据库事务事务是一组操作的逻辑单元,要么全部执行成功,要么全部回滚。在 PostgreSQL 中,事务的开始和结束通过 BEGIN 和 COMMIT 或 ROLLBACK 语句来定义。默认情况下,每个 SQL 语句都在单独的事务中执行,但你可以使用显式的 BEGIN 和 COMMIT 指令来控制事务的边界。 2. 并发控制:并发控制是指在多个用户同时访问数据库时,保证数据的一致和正确。PostgreSQL 使用多版本并发控制(MVCC)机制来实现并发控制。MVCC 使用了版本号(或时间戳)来跟踪事务的可见和一致。 3. 锁机制:PostgreSQL 使用锁来控制并发访问。锁可以对表、行或其他数据库对象进行加锁,以防止其他事务对其进行修改或访问。锁分为共享锁和排它锁,用于控制读取和写入操作之间的冲突。 4. 事务隔离级别:PostgreSQL 支持四种事务隔离级别,分别是读未提交(Read Uncommitted)、读已提交(Read Committed)、可重复读(Repeatable Read)和串行化(Serializable)。这些隔离级别提供了不同的并发控制策略,可以根据应用程序的需求进行配置。 5. 并发控制配置:PostgreSQL 提供了多种配置选项来调整并发控制的能和行为。你可以通过修改配置文件或使用 ALTER SYSTEM 命令来更改这些选项。一些常见的配置选项包括 max_connections(最大连接数)、max_locks_per_transaction(每个事务最大锁数)和deadlock_timeout(死锁超时时间)等。 总而言之,PostgreSQL 提供了强大的数据库事务和并发控制机制,通过锁机制、MVCC 以及事务隔离级别来处理并发操作和保证数据的一致。这些特使得 PostgreSQL 成为处理高并发场景下数据操作的理想选择。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值