OceanBase 数据库基于多版本(Multi Version)及行级别锁实现了数据库的并发控制逻辑、读不加锁、写加互斥锁。做到了读读、读写、写读不相互阻塞,大大提高了系统的并发能力。由于读读并发对数据没有任何修改,因此没有正确性的问题。关于并发控制的设计,下文主要讨论读写、写读、写写三种情况。

事务版本号

  • 语句快照

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

  • 事务快照

    可串行化隔离级别下,事务内的每条语句只能看到该事务开启之前的数据,这一特性的保证,需要在开启事务之前获取一次最新快照版本,该版本我们称为事务快照。RR 隔离级别下,OceanBase 数据库也采用事务级别快照。

  • 提交版本号

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

一致性读

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

强一致性读

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

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

弱一致性读

用户使用弱读的功能,有两种方法:

  • 查询语句添加 hint
  1. Select /*+read_consistency(weak)*/ * from test where c1=1;
  • 设置系统变量

    Session 级别设置:

    set @@ob_read_consistency=2,其中1=FROZEN、2=WEAK、3=STRONG;

    用户级别设置:

    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修改的数据。这里的等待对用户而言是透明的,因此仍能满足“写不阻塞读”的这一结论。

读写并发

对某行数据而言,读写事务和只读事务并发执行,读写事务操作该行之前,已经有只读事务对该行进行读操作,我们称之为读写并发。该场景下,OceanBase 数据库的正确性保证逻辑如下:

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

写写并发

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

  1. 事务 T1 和事务 T2 语句开启,获取相同的语句快照版本号,假设均为 100。

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

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

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

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

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

    ORA-08177: Cannot serialize access for this transaction

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

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

  • 提交该语句之前的事务。或者,

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