5.2 并发控制
快照管理
OceanBase 是基于 MVCC 和行锁实现数据库中的并发控制,涉及如下两种不同类型的快照。
语句快照:读写语句开始执行时获取的版本号。
事务提交快照,有如下两类:
参与者的 Prepare 版本号(Prepare Version)。
事务最终提交的 Commit 版本号(Commit Version),Commit Version 是所有参与者 Prepare Version 中的最大值。
具体如下表所示:
| 名称 | 用途 |
|---|---|
| Read Snapshot | 语句快照 |
| Prepare Version | 事务 prepare version(即 2PC 参与者 prepare log timestamp) |
| Commit Version | 事务的 commit version(所有参与者 prepare log timestamp 最大值) |
| Local Timestamp Service | 1.X 版本提供的本地时钟服务 |
| Global Timestamp Service | 2.X、3.x 版本提供的全局一致的时钟服务,事务支持外部一致性 |
索引结构
OceanBase 采用了 B-Tree、Hash 双索引结构,B-Tree 可以支持范围查询,Hash 可以提供更快的单点查询。

图中各名词解释如下。
Row:ObMvccRow,多版本行。
Insert、update、delete 等:对应的结构为 MvccTransNode,多版本的修改的数据结构。
ObTransCallbackMgr:单个事务 MvccRow 回调的管理链表,回调涉及解锁回调和提交回调,解锁回调会将行锁解开,提交回调会给数据填上 commit 版本号。
MvccRow 具体格式

图中各名词解释如下。
Row lock:行锁。
List head:多版本数据的头指针,指向当前行最新的修改。
Max commit version:当前行最新成功事务的 commit version。
Compaction node:多版本数据修改到一定数量之后会做一次 compact,用于查询优化。OceanBase 数据库中默认修改六次后会做一次 compact,该值由配置项
row_compaction_update_limit控制。Commit version:事务提交版本号。
事务对行操作的时序

图中各名词解释如下。
Register lock cb:用于事务 commit 成功之后,通过 cb 找到 trans node,触发解行锁。
Register commit cb:类似 lock cb,用于回填版本号,修改 trans node 状态 INIT->COMMIT。
Get prepare version:取 max(gts,max_log_ts) 作为该参与者的 prepare version。
Set prepare version:记录当前参与者的 prepare version,用于读写并发控制。
Commit cd:事务提交后的回调,找到对应的 trans node。
Unlock cd:事务提交后解除行锁。
读写并发

读写并发时读不阻塞写;如上图,T1 结束之后 T2 才开始 prepare,T2 commit version 需要保证大于 T1 read snapshot。
写读并发
有关写读并发,根据读事务发生的时间不同,分为以下几种情况:

如上图,T1 的读事务发生在 T2 get prepare version 之后,T1 read snapshot = 250,由于 T2 没有提交,因此读不到 T2 的修改,但 T2 的 commit version = 200。导致 RC 的原则被打破:T1 没有读到 commit version <= 250 的所有修改。
OceanBase 有关该异常的解决方法为增加 before prepare 这一动作,由于 T1 read snapshot > T2 prepare_version,并且 T2 commit version 尚未知道,此时 T1 执行会被卡住,不会返回结果给客户端。
但这一操作还有一个问题,如下图,若 T1 的读事务执行过程中,T2 开始 prepare,如果此时 T2 的 prepare version 依旧是 200,参照情况一的解决方法进行,将导致 T1 的读取一直被卡住,对于读写会产生一定的影响。

此时的解决方法是让读语句尽快的知道 T2 的 prepare version,并保证 T2 的 prepare version 大于读语句的读快照,使读语句无法读到 T2 的修改,从而让读语句尽快结束。因此我们对该过程做了如下优化:
T1 读取之前,推高 max readable ts 更新。
T2 prepare_version = max(max_readable_ts, gts,…)。
若 T1 的读事务发生时,T2 已 commit 完成,此时 T1 read snapshot 根据 T2 commit version 大小关系,决定是否可以读取,如下图所示:

总结
T1 读取过程中,T2 的状态与 T1 相应处理逻辑:
T2 未 prepare,T1 不需要读该事务的数据,即读写并发。
T2 已 prepare 尚未 commit,但 T1 read_snapshot > T2 prepare_version,T1 等待 T2 事务提交。
T2 commit 完成,T1 read_snapshot 根据 T2 commit version 大小关系,决定是否可以读取。
写写并发

写写并发在 RC 级别需要解决 Lost update 的问题,同时在 RR 级别下也需要进行对于不可串行化的判断。
RC:Lost update 检测与处理
OceanBase 里的 update 更新包含 table_scan 和 update 两个动作,table_scan 过程中没有加锁,所以会出现丢失更新的情况,因此,在 update 过程中,如果出现
read_version<MvccRow 上的 max_commit_version的情况,则出现 transaction set consistency(TSC),报错 ret=-6001,重试当前 SQL。RR:不可串行化判断




