OceanBase 的 Oracle 模式不是只支持 2 种隔离级别:读已提交(Read Committed)和可串行化(Serializable)。
作者:任仲禹,爱可生数据库工程师,擅长故障分析和性能优化。
爱可生开源社区出品,原创内容未经授权不得随意使用,转载请联系小编并注明来源。
本文约 1600 字,预计阅读需要 5 分钟。
背景
看到文章标题会有个疑惑,OceanBase 的 Oracle 模式不是只支持 2 种隔离级别:
- 读已提交(Read Committed)
- 可串行化(Serializable)
为什么还讨论在 OBoracle 下使用 可重复读(Repeatable Read) 隔离级别这个问题,一切的起因是交付时遇到的客户疑问:
“我的 JAVA 应用通过 oceanbase-jdbc 访问 OBOracle 数据库,业务上想实现 MySQL 可重复读的效果(即事务内 2 次相同查询看到的数据是不变的),所以我将会话设置为只读 conn.setReadOnly(true)
,但程序运行结果不符合业务预期。”
乍一听没完全理解,沟通后才梳理清楚:
- 客户了解到
set transaction read only;
命令可以实现可重复读的效果。 - 所以应用中配置了
conn.setReadOnly(true)
想达成此目的。
官网原文:设置 ReadOnly,不推荐执行
set session transaction readonly
,推荐使用Connection.setReadOnly(xx)
接口。
此时,问题剩俩:
- OBoracle 中命令
set transaction read only;
为啥能实现可重复读效果? - 配置
conn.setReadOnly(true)
是否正确,不正确该如何配置?
分析
排查涉及的环境:
- OBoracle 模式 323bp10hotfix5
- oceanbase-jdbc 2.4.3
为啥 set transaction read only
能实现可重复读效果?
在 OceanBase 中,只读事务中的所有查询都引用了数据库的同一份快照,从而提供多表、多查询、读取一致的视图。所以在只读事务内 2 次相同查询所看到的数据是一致的,也就实现了可重复读的效果。这在对于多用户更新相同表并且运行多个查询时的场景非常有用,也满足客户的业务需求。
配置 conn.setReadOnly
是否正确?
截取了程序运行堆栈,客户环境用的 Hikari 连接池,调用路径从下往上为【Hikari -> OceanBase-client -> setReadOnly -> setSessionReadOnly】。
最终 setSessionReadOnly
调用的是 set session transaction read only
命令。
按过往 DB 经验,“set transaction read only” 等同于 “set session transaction read only”,[session] 只是默认值而已(MySQL 就是这么做的)。*
在 OBOracle 中实验一下:
set transaction read only
set session transaction read only
通过测试可知,两者的语义和效果是不一样的。虽然都能实现其作用范围内只读,但通过和 OceanBase 支持人员了解到,还是有以下区别:
- 作用范围不同
set transaction read only
仅作用于当前事务,一旦该事务结束(Commit 或 Rollback),设置失效,会话中后续事务不会继承该设置。set session transaction read only
则影响整个会话中的所有事务,一旦设置,会话中接下来的所有事务都会被设置为只读模式,直到会话结束或重新设置为可读写模式。
- 是否可以引用数据库快照
- 仅
SET TRANSACTION
命令开启的只读事务才能引用数据库的快照(继而通过读取一致性视图以获得RR的效果)。 SET SEESION TRANSACTION
命令无法使只读事务获得快照。
- 仅
正确的配置方式
既然 conn.setReadOnly
(set session transaction read only
)无法实现效果,那如何实现 set transaction read only
命令带来的可重复读的效果呢?
OceanBase 产研提供了一个参数:要达成效果,除了需要设置 conn.setReadOnly(True)
,还需在 JDBC option 中添加参数:
- oracleChangeReadOnlyToRepeatableRead=True
该参数在 oceanbase-client 2.4.7 引入,目的是实现可重复读(实质是快照读)的效果。
应用配置完成后,确实有效果。截止本文发布,官网查不到该参数的详细说明,我们先结合源码看下它的实现方式:
对于 setReadOnly
,如果满足了 isOracleMode = true
和 oracleChangeReadOnlyToRepeatableRead = true
情况下,将会把会话的隔离级别设置为 setTransactionIsolation(readOnly ? 4 : 2)
。
本例场景下, readOnly
的值被设置是 True
,那么传递给 setTransactionIsolation
方法的值就是 4 。
由上可知,当值为 4 时,JDBC 将会传递下述 SQL 给后端 OBServer:
- SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ
前文提到 OBoracle 不是仅支持 RC 和 Serializable 吗,那该命令发到 OB上的行为是怎样的? 继续测试下:
结果是 OBoracle 可以实现可重复读的效果,且通过客户端命令查询到当前会话被设置为了 REPEATABLE READ。
Repeatable Read 和 Serializable
最后再简单说明下,官网提到 OB 的 MySQL 模式支持 3 种隔离级别(RC、RR、Serializable),Oracle 模式支持 2 种(RC、Serializable)。但是实际在 OceanBase 数据库中只实现了 2 种隔离级别,即读已提交(RC)和可串行化(Serializable)。
- 当用户指定 RR 隔离级别时,实际使用的是 Serializable。也就是说,OceanBase 数据库的 RR 隔离级别更加严格,不会出现幻读的异常情况。
- 但在底层实现上,OceanBase 数据库的 Serializable 隔离级别实际使用 快照隔离(Snapshot Isolation,SI),不能保证严格的可串行化。
结论
应用通过 oceanbase-client 驱动访问 OceanBase Oracle 模式数据库时,要想实现 Repeatable Read(可重复读) 的效果,除了需要设置 setReadOnly
为 True,还需要满足:
- oceanbase-client >= 2.3.8 版本
- JDBC Option 中配置 oracleChangeReadOnlyToRepeatableRead=True
OceanBase Oracle 模式数据库中,会话可以被设置为 RR 隔离级别,但会话变量只是显示为 RR ,实际底层实现上用的是快照隔离(Snapshot Isolation)。