• 语句快照

    RC 隔离级别下,每条语句都能读到该语句开始之前的最新数据,这一特性的保证,需要在语句开启之前获取一次最新快照版本,该版本我们称为语句快照。

  • 提交版本号

    事务提交过程中需要为本次修改的数据确定一个版本号,我们称之为事务的提交版本号。GTS 打开场景下,提交版本号从 GTS Leader 获取并确认;GTS 关闭场景下,提交版本号由数据所在 server 共同进行协商。

OceanBase 数据库支持两种类型的读请求,强一致性读和弱一致性读。强一致性读要求根据快照信息读取 leader 上的数据;弱一致性读允许读取某一个稍旧的版本的数据。

强一致性读

  • 跨机查询的语句,需要获取全局一致的快照,否则无法保证强读的一致性。因此该情况下,需要确保 GTS 是打开的。

  • 单机的查询,理论上不需要依赖 GTS,通过获取待读分区的最大已经提交的版本号,取其最大值作为读快照,就能保证强读的一致性。

弱一致性读

  • 查询语句添加 hint
  • 设置系统变量

    Session 级别设置:

    用户级别设置:

    set global ob_read_consistency=2;

    OceanBase 数据库维护了一个租户级别最大安全可读的版本号,弱读语句快照由该版本号决定。非 RC 隔离级别的弱读没有意义,因此 OceanBase 数据库只在 RC 隔离级别下支持弱读。

    下文讨论的并发控制,没有特殊说明,均指的是强一致性读。

对某行数据而言,读写事务和只读事务并发执行,只读事务读取过程中,该行正在被读写事务修改且尚未提交,我们称之为写读并发。

该场景下,OceanBase 数据库的正确性保证逻辑如下:

读写事务 T1 提交过程中,确定 commit 成功之前,该事务的提交版本号(commit version)是无法知道的。此时如果有读事务 T2 并发读取该行,是否能够读到该事务的修改呢?需要分情况讨论:如果 T1 的提交版本号(commit version) < T2 的读快照(read version),则 T2 需要读到 T1 的修改;否则 T2 不需要读到 T1 的修改。

具体而言,如果 T1 此时尚未收到用户发来的 commit,那么 T2 的 read version 一定会小于 T1 的 commit version,因此该情况下 T2 不需要等T1提交结束;如果T1此时处于正在提交过程中,则T2需要等T1确定 commit version,才能决定是否需要读T1修改的数据。这里的等待对用户而言是透明的,因此仍能满足“写不阻塞读”的这一结论。

只读事务读取过程中,读写事务还在执行,客户端尚未发出 commit,因此 T1 的 read version 一定小于 T2 的 commit version,T1 不需要读到 T2 的修改。T1 和 T2 不会相互等待。

写写并发场景,主要通过对行加互斥锁来实现,即前一个事务尚未提交结束,后一个需要操作同一行的事务需要等待。写写并发场景,lost update 问题是重点需要解决的。考虑事务 T1 和事务 T2,两者并发单分区表 A 中的同一行 R1,待更新的列上有局部索引。执行流程如下:

  1. 事务 T1 语句执行过程中,先于 T2 获取行锁,并执行更新。事务 T2 出现行锁冲突,重试等行锁释放。

  2. 事务 T1 提交结束,行锁释放。

事务 T2 该如何执行?如果 T2 直接持有行锁,继续修改,将会出现数据一致性问题,因为 T2 并没有看到 T1 的修改,直接将其数据进行了覆盖。OceanBase 数据库为了解决该问题,T2 加上 R1 的行锁之后,会进行一次 Double Check,如果发现存在该问题,不同隔离级别下的处理方法不同:

  • RC 隔离级别下重试执行该语句。

  • 可串行化隔离级别下,该语句不能重试并且需要向用户返回如下错误:

    ORA-08177: Cannot serialize access for this transaction

    当数据库返回 ORA-08177 时,用户可以根据业务的情况做出决定:

  • 回滚事务,然后重新执行整个事务。或者,

  • 回滚到某个 save point 然后执行其他分支。