头图

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),但程序运行结果不符合业务预期。”

乍一听没完全理解,沟通后才梳理清楚:

  1. 客户了解到 set transaction read only; 命令可以实现可重复读的效果。
  2. 所以应用中配置了 conn.setReadOnly(true) 想达成此目的。
官网原文:设置 ReadOnly,不推荐执行 set session transaction readonly,推荐使用 Connection.setReadOnly(xx) 接口。

此时,问题剩俩:

  1. OBoracle 中命令 set transaction read only; 为啥能实现可重复读效果?
  2. 配置 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.setReadOnlyset session transaction read only)无法实现效果,那如何实现 set transaction read only 命令带来的可重复读的效果呢?

OceanBase 产研提供了一个参数:要达成效果,除了需要设置 conn.setReadOnly(True) ,还需在 JDBC option 中添加参数:

  • oracleChangeReadOnlyToRepeatableRead=True

该参数在 oceanbase-client 2.4.7 引入,目的是实现可重复读(实质是快照读)的效果。

应用配置完成后,确实有效果。截止本文发布,官网查不到该参数的详细说明,我们先结合源码看下它的实现方式:

对于 setReadOnly,如果满足了 isOracleMode = trueoracleChangeReadOnlyToRepeatableRead = 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)。


爱可生开源社区
426 声望207 粉丝

成立于 2017 年,以开源高质量的运维工具、日常分享技术干货内容、持续的全国性的社区活动为社区己任;目前开源的产品有:SQL审核工具 SQLE,分布式中间件 DBLE、数据传输组件DTLE。