OceanBase 数据库基于多版本(Multi Version)及行级别锁实现了数据库的并发控制逻辑、读不加锁、写加互斥锁。做到了读读、读写、写读不相互阻塞,大大提高了系统的并发能力。由于读读并发对数据没有任何修改,因此没有正确性的问题。关于并发控制的设计,下文主要讨论读写、写读、写写三种情况。
事务版本号
- 语句快照
RC 隔离级别下,每条语句都能读到该语句开始之前的最新数据,这一特性的保证,需要在语句开启之前获取一次最新快照版本,该版本我们称为语句快照。
- 事务快照
可串行化隔离级别下,事务内的每条语句只能看到该事务开启之前的数据,这一特性的保证,需要在开启事务之前获取一次最新快照版本,该版本我们称为事务快照。RR 隔离级别下,OceanBase 数据库也采用事务级别快照。
- 提交版本号
事务提交过程中需要为本次修改的数据确定一个版本号,我们称之为事务的提交版本号。GTS 打开场景下,提交版本号从 GTS Leader 获取并确认;GTS 关闭场景下,提交版本号由数据所在 server 共同进行协商。
一致性读
OceanBase 数据库支持两种类型的读请求,强一致性读和弱一致性读。强一致性读要求根据快照信息读取 leader 上的数据;弱一致性读允许读取某一个稍旧的版本的数据。
强一致性读
- 跨机查询的语句,需要获取全局一致的快照,否则无法保证强读的一致性。因此该情况下,需要确保 GTS 是打开的。
- 单机的查询,理论上不需要依赖 GTS,通过获取待读分区的最大已经提交的版本号,取其最大值作为读快照,就能保证强读的一致性。
弱一致性读
用户使用弱读的功能,有两种方法:
- 查询语句添加 hint
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,待更新的列上有局部索引。执行流程如下:
- 事务 T1 和事务 T2 语句开启,获取相同的语句快照版本号,假设均为 100。
- 事务 T1 语句执行过程中,先于 T2 获取行锁,并执行更新。事务 T2 出现行锁冲突,重试等行锁释放。
- 事务 T1 提交结束,行锁释放。
事务 T2 该如何执行?如果 T2 直接持有行锁,继续修改,将会出现数据一致性问题,因为 T2 并没有看到 T1 的修改,直接将其数据进行了覆盖。OceanBase 数据库为了解决该问题,T2 加上 R1 的行锁之后,会进行一次 Double Check,如果发现存在该问题,不同隔离级别下的处理方法不同:
- RC 隔离级别下重试执行该语句。
- 可串行化隔离级别下,该语句不能重试并且需要向用户返回如下错误:
ORA-08177: Cannot serialize access for this transaction
当数据库返回 ORA-08177 时,用户可以根据业务的情况做出决定:
- 回滚事务,然后重新执行整个事务。或者,
- 提交该语句之前的事务。或者,
- 回滚到某个 save point 然后执行其他分支。