感谢官网< 并发控制概述> 的书写,很有启发性。 读后一个几个小的疑问, 想要确认一下:
-
“如图所示,事务 12 进入提交阶段,并设置状态为 PREPARE,设置本地事务版本号为本地 最大读时间戳
120 与取 GTS 为 150 的最大值 150 作为 本地事务版本号
。”
我的问题是:
a) 因为prepare并不影响可见性, 为什么prepare 需要产生一个版本号? 如果是在commit 结束(第二阶段), 我认为是需要一个版本号的。
b). 访问gts 代表着延迟, 为什么需要去访问gts。 根据 [逸畅] 大佬的在 官方文档中《并发控制概述》部分疑问 - 社区问答- OceanBase社区-分布式数据库 的回复, [quote=“逸畅, post:6, topic:35609860”]
由于 GTS 是用于保证“外部一致性”的,与“并发控制”本身无关,即不使用 GTS 也能保证并发控制的正确性,因此本文在尽可能弱化这一概念
[/quote], “外部一致性维护的用意又是什么呢, 有什么真实的应用场景吗?
-
原文: “当读取到 PREPARE 状态的事务时,… 但是若 本地时间戳
小于读时间戳,那么无法确认这个时间戳最后的 全局提交时间戳
和读时间戳的关系,因此解决方案是,优雅地等在这行的事务上(内部称为 lock for read
,两阶段提交在 OceanBase 数据库的假设中应该会是很快完成的过程。如下图所示,读取请求 r3 以 140 作为读版本号进行读取,会先推高 最大读事务时间戳
,保证之后事务以大于 140 的 本地提交版本/全局提交版本
提交,然后等待两阶段提交状态且 本地提交时间戳
为 130 的事务最后决定 全局提交时间戳
和读时间戳 140 的关系。”
我的问题是:这里的 “本地时间戳” 是 事务prepare 完成时的时间戳吗? 如果是的话,现实中表达的含义是: 我发出请求的那一个刻,该事务正在提交。 但终究是没有提交完成的。 我们为什么要等而不是直接当成 事务未提交完成 来对待呢?
-
读时间戳是根据 “最大提交时间戳” 来算出来的, 那么在系统刚刚启动的时候, 最大提交时间戳 是怎么设置的? WAL 恢复出来的吗?
-
当一个2pc 事务提交以后,计算出的 最大提交时间戳 是如何让所有节点知道的呢? 通过广播 还是 谁需要需要读的节点向中心节点去读呢? 感觉前者整体性能会更好一些。
-
对于单节点的事务(非2pc),它的提交时间戳 是不是只用更新本地即可, 从而避免远程广播的代价。 这样 该节点的读请求 依然可以感知这个事务, 别的节点也不需要这个信息(?). 只是将来收到别人的广播时, 取一个Max 值即可。
-
是否存在【预取版本号】 来优化网络开销的设计。 很多人说有这样的逻辑, chatgpt 也说有, 但我很难理解其正确性。 根据我当前对文档的理解,只有在事务提交的时候,广播一次, 我们几乎没有场景需要远程获得, 所以【预取版本号】似乎是没必要的。 我还是希望官方的同学能出面澄清一下。
感谢!
淇铭
#3
感谢,你提供的文档中确实很好的解释了 <外部一致性> 的问题, 这个问题确实和 OBServer 的 MVCC 机制本身无关了, 个人更倾向于 文档中直接不写, 或者描述为, “如果需要外部一致性, 我们此时需要将版本号注册到 GTS 服务,并取得一个较值作为本地版本号。 否则 可以省略掉GTS 服务的沟通环节”。
所以这篇文档回答了我 【问题1】 中的 【部分问题】, 其他问题看起来还没有回答?
为什么 PREPARE 阶段需要产生版本号?
- 在两阶段提交协议中,PREPARE 阶段的主要目的是为了确保所有参与者都准备好提交事务。在这个阶段产生的版本号(
本地事务版本号
)是用于后续的提交阶段,确保事务的提交版本号不会小于 PREPARE 阶段的版本号。这样做有助于保证事务的原子性和一致性。虽然 PREPARE 阶段本身不影响可见性,但它为后续的提交阶段提供了必要的信息。
–》 既然是必要,如果我们【不要】这个信息会出现什么异常呢?
-
为什么选择等待而不是直接忽略?
- 如果直接忽略 PREPARE 状态的事务,可能会导致读取结果不一致。等待是为了确保读取的结果是正确的,即确保读取到的数据要么是事务提交后的最新状态,要么是事务回滚后的状态。这样可以保证读取的一致性和正确性。
–》 有没有【具体例子】来说明什么【不这样做就会有问题】。
我当前的理解是上述的2个环节都是可以简化掉的。
其他问题都没有疑问了, 感谢回复。
淇铭
#7
我所有的问题都是从这篇文章开始的~ 不过我确实是在 一开始发帖的时候,只写了 “感谢官网< 并发控制概述> ”,而没有加上文章的链接。
淇铭
#9
1、 为什么 PREPARE 阶段需要产生版本号?
但是这个里面确实有解释呀 提交请求处理 读请求处理 写请求处理 其实都和你说的 PREPARE 阶段有关系
在你说的【既然是必要,如果我们【不要】这个信息会出现什么异常呢?】 其实如果没有PREPARE 阶段这几个处理请求 都会有问题 这个文档中其实也有解释
2、 为什么选择等待而不是直接忽略?
这个目前看的只有文档解释 没有用例 这个暂时需要你加强理解
这里能请对应的内核看一下吗? 我感觉这里是一个潜在的性能优化,并且可以让代码更加简洁。如果不应该改变,也会有更加明确的理由。
这个阶段确实是必须的。 假设不考虑 prepare 阶段不更新版本号:
node1:
data-a : local_commit_ts: 100
node2:
data-x: local_commit_ts: 104
data-y: local_commit_ts: 104
2pc coordinator 会将global txn commit ts 设置为104,并记录事务状态 更新到 全局事务表。
如果在 local commit 104 之后, 全局事务表更新之前, 需要判断 data-x 的可见性, 此时读版本号为 104, 按照我的说法,读版本号符合要求, 因为全局事务状态没有提交,所以不可见。
随后, 全局事务表更新了。
随后,读到了data-y, 发现 读版本号和全局事务状态都通过,所以data-y可见了。
此时读不一致产生了,因为data-x 和 data-y 属于同一个事务,结果 一行可见,一行不可见了。