首批通过分布式安全可靠测评,为关键业务系统打造
REPEATABLE READ 问题汇总
更新时间:2024-06-17 08:21
适用版本
OceanBase 数据库 V3.2.x 版本。
问题一 set autocommit = 1 未触发隐式提交
问题现象
具体问题如下图所示,右侧连接的事务隔离级别为 repeatable read(RR),可以发现 set autocommit = 1 未触发隐式提交,因此事务一直复用旧版本读取,从而导致无法获取到最新数据。

问题原因
autocommit 为原生 MySQL 下的变量,在原生 MySQL 下,set autocommit = 1 触发隐式提交的条件包含:
- 会话上已开启事务。
- 会话上的 autocommit 为 0。
由于原生 Oracle 没有 autocommit,且 OceanBase 数据库支持 autocommit,因此 Oracle 租户下的 autocommit 行为可参考原生 MySQL。目前 OceanBase 数据库的具体行为如下。
- 在 V3.x 版本中
- Oracle 租户:对于一条 SELECT 语句开启的事务,
set autocommit = 1不会隐式提交事务,因此下一条 SELECT 语句不会重新获取事务快照,无法读到最新提交的数据。对于非 SELECT 语句开启的事务,set autocommit = 1会隐式提交事务。 - MySQL 租户:对于 SELECT 和非 SELECT 语句开启的事务,
set autocommit = 1都会隐式提交事务。
- Oracle 租户:对于一条 SELECT 语句开启的事务,
- 在 V4.x 版本中
- Oracle 租户:对于 SELECT 和非 SELECT 语句开启的事务,
set autocommit = 1都会隐式提交事务。 - MySQL 租户:对于 SELECT 和非 SELECT 语句开启的事务,
set autocommit = 1都会隐式提交事务。
- Oracle 租户:对于 SELECT 和非 SELECT 语句开启的事务,
该问题场景下,会话上的 autocommit 为 0(满足第二个条件),但由于 select 不开启事务(Oracle 兼容),导致第一个条件不满足,因此未触发隐式提交。
注意
如果通过 JDBC 连接到 OBServer,且会话上 autocommit 为 1,会话上再执行 commit,JDBC 不会真实的执行 commit。
因此,如果客户端通过 JDBC 连接到数据库,且执行逻辑如下,则会遇到该问题:
前置条件:OceanBase Connector/JDBC 的 url 中增加 oracleChangeReadOnlyToRepeatableRead=TRUE(兼容Oracle)。
- conn.setReadOnly(true)
- conn.setAutoCommit(false)
- select
- conn.setAutoCommit(true)
- conn.commit();
- 第 2 步至第 5 步循环执行。
解决方法及规避方式
为了规避该问题,可以采用如下方法:
- 去掉
oracleChangeReadOnlyToRepeatableRead,此时会话开启 readonly 模式。 - 连接上去掉 setReadOnly 操作,避免设置会话事务隔离级别为 RR,从而每个语句都会获取最新的读取版本号。
- 调整第 4 步和第 5 步的顺序。先执行
commit,再执行setAutoCommit(true)。
问题二 JDBC connection.commit 未触发提交
问题现象
在执行 JDBC 中 Connection 对象的 commit 函数时,发现当前事务未提交,SQL Audit 中查询不到 COMMIT 请求。 本示例使用 Oracle 租户,简化后的业务场景如下。
Connection conn = DriverManager.getConnection(url, user, password);
Statement ps = conn.createStatement();
// oracleChangeReadOnlyToRepeatableRead参数会将setReadOnly操作改成设置会话隔离级别为RR
conn.setReadOnly(true);
conn.setAutoCommit(false);
// 执行一条SELECT语句
ps.execute("select * from test where id = xxx");
// commit未能提交事务
conn.commit();
问题原因
JDBC 驱动会判断当前会话是否处于事务中(是否设置了 in_trans 标记,该标记由 OBServer 返回),只有处于事务中时,conn.commit 才会真正发送 COMMIT 命令。
OBServer 判断是否设置 in_trans 标记的逻辑如下:
- 如果不是只读语句,设置 in_trans。
- 如果是只读语句,若该语句满足:(获取了有效的快照) && (autocommit=0) && (事务隔离级别为 RR 或者 Serializable || 租户的
ob_proxy_readonly_transaction_routing_policy配置项为true),设置 in_trans。 - 其他情况不设置 in_trans。
SELECT 语句是只读语句,获取了有效的快照,且业务通过设置 conn.setReadOnly(true) 将隔离级别改为 RR,又设置了 autocommit=0,按理说应该会设置 in_trans。
但由于代码缺陷,在实现上述第二点判断逻辑时,对于隔离级别只考虑了 Serializable 的情况,没有考虑 RR 的情况,并且将 ob_proxy_readonly_transaction_routing_policy 设置为 false,所以最终 in_trans 标记没有被设置,也就导致了 conn.commit 没有提交事务。
影响版本
OceanBase 数据库 V3.2.x 版本。
问题三
语句重试到超时,报错 4138。
问题现象
租户模式为 ready only,事务隔离级别为 REPEATABLE-READ(RR)。 可以通过如下 SQL 查询事务隔离级别:
obclient> show variables like 'transaction_isolation';
+-----------------------+-----------------+
| VARIABLE_NAME | VALUE | |
+-----------------------+-----------------+
| transaction_isolation | REPEATABLE-READ |
+-----------------------+-----------------+
1 row in set (0.003 sec)
在 RR 隔离级别下,当语句读取快照过旧,并且历史版本已经被回收的情况下,预期该语句直接返回 -4138 错误码。但该语句将进行重试,重试到语句超时。
问题原因
RR 隔离级别下,语句执行过程中 4138 错误码会进行重试。
问题的风险及影响
如果语句超时时设置时间较大,语句长时间不结束。
影响版本
OceanBase 数据库 V3.2.x 版本。
解决方法及规避方式
改用 RC 隔离级别。