OceanBase 数据库提供了两种一致性级别(Consistency Level):STRONG 和 WEAK。STRONG 指强一致性,读取最新数据,请求路由给主副本;WEAK 指弱一致性,不要求读取最新数据,请求优先路由给从副本。OceanBase 数据库的写操作始终是强一致性的,即始终由主副本提供服务;读操作默认是强一致性的,由主副本提供服务,用户也可以指定弱一致性读,由从副本优先提供服务。
一致性级别的指定方式
有两种方式指定一致性级别:
通过
ob_read_consistency
系统变量指定设置 Session 变量,影响当前 Session
obclient> SET ob_read_consistency = WEAK; obclient> SELECT * FROM t1; -- 弱一致性读
复制设置 Global 变量,影响之后新建的所有 Session
obclient> SET GLOBAL ob_read_consistency = STRONG;
复制
指定 Hint 方式
指定 WEAK Consistency,优先级高于
ob_read_consistency
obclient> SELECT /*+READ_CONSISTENCY(WEAK) */ * FROM t1;
复制指定 STRONG Consistency
obclient> SELECT /*+READ_CONSISTENCY(STRONG) */ * FROM t1;
复制
SQL 语句的一致性级别
写语句 DML (INSERT/DELETE/UPDATE):强制使用 STRONG Consistency,要求基于最新数据进行修改。
SELECT FOR UPDATE
(SFU):与写语句类似,强制使用 STRONG Consistency。只读语句
SELECT
:用户可以配置不同的 Consistency Level,满足不同的读取需求。
事务的一致性级别
弱一致性读的最佳实践是为不在事务中的 SELECT 语句指定 WEAK 一致性级别,它的语义是确定的。对于显式开启事务的场景,语法上,OceanBase 数据库允许不同的语句配置不同的一致性级别,这样会让用户很困惑,而且如果使用不当,SQL 会报错。
原则:
一致性级别是事务级的,事务内所有语句采用相同的一致性级别。
由事务的第一条语句决定事务的一致性级别,后续的 SELECT 语句如果指定了不同的一致性级别,则强制改写为事务的一致性级别
写语句和 SFU 语句只能采用 STRONG,如果事务级一致性级别为 WEAK,则报错
OB_NOT_SUPPORTED
。
下面举例说明。
BEGIN; -- 修改语句,consistency_level=STRONG,整个事务应该是 STRONG insert into t1 values (1); -- SQL自身的 consistency_level=WEAK,但由于第一条语句为 STRONG, -- 因此这条语句的 consistency_level 强制设置为 STRONG select /*+READ_CONSISTENCY(WEAK) */ from t1; COMMIT; BEGIN; -- SFU属于修改语句,consistency_level=STRONG,整个事务应该也是STRONG select * from t1 for update; -- SQL自身的consistency_level=WEAK,但由于第一条语句为STRONG, -- 因此这条语句的consistency_level强制设置为STRONG select /*+READ_CONSISTENCY(WEAK) */ from t1; COMMIT; BEGIN; -- 第一条语句为WEAK select /*+READ_CONSISTENCY(WEAK) */ from t1; -- 虽然本条语句为STRONG,但是会继承第一条语句的consistency level,会强制设置为WEAK select * from t1; COMMIT; BEGIN; -- 第一条语句为WEAK select /*+READ_CONSISTENCY(WEAK) */ from t1; -- 修改语句,必须为STRONG,由于第一条语句为WEAK,这里会报错:NOT SUPPORTED insert into t1 values (1); -- SFU属于修改语句,必须为STRONG,这里同样会报错:NOT SUPPORTED select * from t1 for update; COMMIT;
复制
因此,对于单条 SQL 而言,一致性级别的确定规则优先级从大到小可以概括为:
根据语句类型确定的一致性级别,例如 DML 和 SFU 必须采用 STRONG。
事务的一致性级别,如果语句在事务中,而且不是第一条语句,则采用事务的一致性级别。
通过 Hint 指定的一致性级别。
系统变量指定的一致性级别。
缺省采用 STRONG。
与隔离级别关系
STRONG 支持所有的隔离级别。
WEAK 仅支持读已提交
READ COMMITTED
隔离级别,其他隔离级别下会报错OB_NOT_SUPPORTED
。
弱一致性读配置项
V2.2.x 之前的版本
名称 | 范围 | 语义 |
---|---|---|
enbale_causal_order_read | 集群级 | 是否开启单调读,默认为 false |
max_stale_time_for_weak_consistency | 集群级 | 弱一致性读最大落后时间,默认值是 5 秒 |
V2.2.x 之前的版本仅支持 Proxy 级别的单调读,即只要客户端始终访问同一个 Proxy,就可以保证单调读。具体实现方式是在 Proxy 上维护单调递增的弱一致性读版本号。如果客户端跨 Proxy 访问,则不保证单调读。如果用户需要集群级别单调读,则需要使用 V2.2.x 及之后的版本。
V2.2.x ~ V3.x 版本
名称 | 范围 | 语义 |
---|---|---|
enable_monotonic_weak_read | 租户级 | 是否开启单调读,默认为 false |
max_stale_time_for_weak_consistency | 租户级 | 弱一致性读最大落后时间,默认值是 5 秒 |
weak_read_version_refresh_interval | 集群级 | 弱一致性读版本号刷新周期,默认值 50 毫秒 |
各个配置项具体含义如下:
enable_monotonic_weak_read
:租户级单调读开关弱一致性读会路由到不同副本上,不同副本上读到的数据新旧没有保证;单调读开关打开后,OceanBase 数据库保证读到的数据版本不回退,保证单调性。一个典型的应用场景是保证因果序:两个事务 T1 和 T2,T1 提交之后,T2 才提交,如果客户端读到了 T2 事务的修改,那么之后一定可以读到 T1 事务的修改。
max_stale_time_for_weak_consistency
:弱一致性读最大落后时间OceanBase 数据库弱一致性读提供有界旧保证,即保证读到的数据最多落后
max_stale_time_for_weak_consistency
时间,默认配置值是 5 秒,支持租户级配置。正常情况下,各个分区的备副本落后时间在 100 毫秒到 200 毫秒,弱一致性读的时效性在百毫秒级别;当出现网络抖动、无主等情况,弱一致性读的时效性会降低,一旦一个副本落后时间超过
max_stale_time_for_weak_consistency
,该副本将不可读,内部重试机制会重试其他有效副本;如果所有副本都不可读,则持续重试,直到语句超时。当开启单调读开关后,OceanBase 数据库内部会为每个租户维护一个 Cluster 级别的弱一致性读版本号,该版本号也满足
max_stale_time_for_weak_consistency
约束。它的生成方式是统计租户下所有分区副本回放进度的最小值,如果某些分区副本落后时间超过max_stale_time_for_weak_consistency
,则不统计该副本。目前机制下,一个落后的副本会影响整体单调读的版本号,例如有两个分区的两个副本,一个副本落后 100ms,一个副本落后 1 秒,那么整体的单调读版本号是 1 秒。我们认为副本长时间落后不会是常态,正常情况下,单调读版本号都应该在百毫秒级别。weak_read_version_refresh_interval
:弱一致性读版本号刷新周期弱一致性读版本号刷新周期影响读取数据的新旧程度,它配置的值不能大于
max_stale_time_for_weak_consistency
。当它配置为 0 时,弱一致性单调读功能关闭,即不再维护 Cluster 级别弱一致性读版本号。另外,它是集群级别配置项,不支持租户级别配置。
2.2.x 之前版本
名称 | 范围 | 语义 |
---|---|---|
enbale_causal_order_read | 集群级 | 是否开启单调读,默认为 false |
max_stale_time_for_weak_consistency | 集群级 | 弱一致性读最大落后时间,默认为 5 秒 |
2.2 之前版本仅支持 Proxy 级别单调读,即只要客户端始终访问同一个 Proxy,就可以保证单调读。具体实现方式是在 Proxy 上维护单调递增的弱一致性读版本号。如果客户端跨 Proxy 访问,则不保证单调读。如果用户需要集群级别单调读,需要使用 2.2 及之后的版本。
V4.x 版本
对于 4.x 版本,max_stale_time_for_weak_consistency
和 weak_read_version_refresh_interval
两个配置项的功能保持与之前的版本一致。解除了对 enable_monotonic_weak_read
的依赖,弱读默认就是租户级别的单调读,不再支持关闭。
弱一致性读时间戳
弱一致性读,通常是指从副本、备库上的查询语句。OceanBase 数据库的弱一致性读依旧返回事务一致性点, 不会出现返回未提交事务和一半事务的情况。
弱一致性读时间戳,包括两个方面:
弱读执行之前,需要确定一个读快照数据。
分区自身实时维护一个最大安全可读的位点,凡是读快照大于该位点的读请求,均不能读该副本。
时间戳生成方式
OceanBase 数据库弱读一致性分为两大类:单调读和非单调读。不同的能力,时间戳生成的方式不同。
单调读
单调读是指根据读请求发起的绝对时间,后发起的请求使用的读快照一定不比前者小,确保读到的数据不会出现回退。这里的单调是针对语句读快照而言的。单调读的保证依赖集群级别单调弱读版本号,全局版本号需要做到不回退。
全局版本号的生成来源于每个 OBServer 维护的最小最大安全版本号,该版本号的生成依赖本机日志同步的进度。对于 OceanBase 数据库而言,事务的日志数据,由三个模块来维护,apply service
、replay_service
和 Transaction
:
apply service
负责事务日志的本地落盘、发送备机、接收 Leader 同步的日志等操作。对备机而言,它为每个日志流维护了一个滑动窗口,将收到的日志,按照 logid 从小到大管理起来。日志从滑动窗口中顺序滑出,提交给replay_service
去回放。replay_service
负责日志的回放,它将同一个事务的多条日志 Hash 到同一个工作线程,保证同一个事务的日志顺序回放,但是同一个分区的多个事务是并发回放的。对一个分区的日志而言,日志按照提交时间戳有序串到链表中,由多个线程并发回放。Transaction
负责事务状态的管理,Transaction
执行日志的回放操作,即将 Redo 数据回放到 MemStore,并记录事务的状态。
由上描述可知,一条日志从 apply service
接收到提交 replay_service
回放任务,再通过 Transaction
回放事务日志,为保证单个分区备机读不会读到一半的事务,需要找到该分区备机读的安全版本号,保证该版本号之前的所有事务都已经回放完成。计算公式如下:
slave_read_ts = min(replay_service_ts_ts, apply_service_ts, trans_service_ts) - 1
其中:
apply_service_ts
表示apply service
滑动窗口中下一条将要滑出或将要生成的日志 Timestamp。apply_service_ts
表示该日志流尚未回放日志的 Timestamp 最小值。trans_service_ts
表示当前所有正在回放事务的prepare_log_ts
的最小值。
举例说明:假设 apply service
的滑动窗口中存在两条日志,logid 和日志的提交时间戳分别为(10,100),(11,200);replay_service
中,该日志流尚未回放的日志分别为(7,70),(8,80)
;Transaction
中,只有一个正在回放的事务,刚刚回放完 redo 和 prepare log,并且 prepare log
的 logid 为 9
,prepare_log_ts
=90;此时该分区的 安全版本号=min(100, 70, 90) - 1 = 69
。
非单调读
相对于单调读,非单调读保证的能力较弱。非单调读则不保证前后发起的请求快照递增,由于同一份数据不同副本同步进度的差异,如果前后两次读取不同的副本,数据可能出现回退。
原子性
不论单调读还是非单调读,对单个分区同一个事务修改的数据,均能保证原子性。不论何种场景,只要读快照小于单个分区维护的最大安全可读版本号,读到的数据一定具备原子性,这是原子性保证的原则。
因此设计上,需要引入分区是否可读的校验,对于不可读的分区,让 SQL 执行引擎重试其他副本。