问题现象
问题出现的场景:
一个带触发器的 DML 语句执行完成后。
接着执行查询语句,可能出现两种异常情况。
a. 读本事务的修改,只能读到触发器的修改,读不到主表上的修改。
b. 读已提交的修改(别的事务),只能读到触发器修改的表上的修改,读不到主表上的修改。
以上 2 类异常都属于违反 read-consistency,即读不到已提交数据的完整修改或者读不到本事务已经完成的所有修改。
是否有报错:没有报错。
参见如下例子:
-- T1_with_trigger 表上有一个 update row level 触发器,它的执行会插入 T1_mirror 表
-- Txn1 开始执行 xa 事务修改 T1_with_trigger 表
call xa_begin_oracle_procedure(3031); -- 开始 xa 事务
-- 执行更新语句,语句中表有触发器,会判定为 uncertain-plan
-- 同时假设有别的事务 Txn2 在 T1_with_trigger 和 T1_mirror 上提交了修改
update T1_with_trigger set v = v + 1;
-- 接着执行查询该表 T1 的语句子,会出现异常:
-- 查询不到刚刚 update 的结果
-- 查询不到 Txn2 在 T1 上的修改
select * from T1_with_trigger;
-- 可以查询到触发器锁修改的表上的写入
select * from T1_mirror
关键诊断信息
触发条件
一个带触发器的 DML 语句执行完成后。
接着执行查询语句,可能出现两种异常情况。
a. 读本事务的修改,只能读到触发器的修改,读不到主表上的修改。
b. 读已提交的修改(别的事务),只能读到触发器修改的表上的修改,读不到主表上的修改。
以上 2 类异常都属于违反 read-consistency,即读不到已提交数据的完整修改或者读不到本事务已经完成的所有修改。
问题原因
产品 BUG 引起的。
详细说明
由于 XA 事务的执行走的是版本 V1 的语句处理逻辑。在这个逻辑中依赖一个事务描述符上的变量标识当前事务是否执行过 DML 语句。 当 DML 语句的计划为带 trigger 等复杂的情况时,在语句执行开始时代码 BUG 漏将这个标识设置为 true。 在语句执行的优化路径上,依赖这一标记对于 SELECT 语句的执行进行 fast-path 优化:
当这一标记为 false 时,SELECT 语句仅仅取快照,不做其他信息获取。
当这个语句访问前序 DML 语句修改过的分区时,会获取到事务上下文,使用其上面的快照来读取。
在事务读取可见性判断的路径上
读取未提交事务的修改,使用 seq_no 来进行可见性判断:
stmt_seq_no > trans_node.data_seq_no
由于 fast-path 的优化并未推高语句的 stmt_seq_no,导致 stmt_seq_no 与最后执行的语句的 seq_no 一样。 从而无法满足上述可见性判断。导致无法被读取到。
读已提交事务的修改,使用快照来进行可见性判断:
read_snapshot_version > trans_node.trans_version
由上述 详细说明 这一小节中 在语句执行的优化路径上,依赖这一标记对于 SELECT 语句的执行进行 fast-path 优化 的第 2 点可知:路径导致快照从事务上下文获取,而事务上下文上的快照是前序 DML 语句执行时设置上去的,最终导致使用的快照偏小。 由于 RC 隔离级别下主表先获取快照,trigger 语句后获取快照,因此 trigger 语句的快照大于主表语句的快照。从而在上诉读取路径时使用的快照主表为 Ver1(假设 100),读取 trigger 语句写入的分区的快照为 Ver2(200)。 如果一个已提交事务的修改版本为 Ver3(150),则可以发现,Ver1 < Ver3 < Ver2 在主表上读取不到。在 trigger 写入的表上可以读到的。
问题的风险及影响
在 XA 事务中使用复杂 DML 的事务可能存在查询结果错误的正确性问题。
影响租户
影响 OceanBase 数据库中的 Oracle 租户,对于 SYS 租户和 MySQL 租户无影响。
影响版本
OceanBase 数据库 V3.2.3 GA(oceanbase-3.2.3.0-20220418212020)及之后版本、V3.2.4 GA(oceanbase-3.2.4.0-100000072022102819)及之后版本。
解决方法
升级至问题已修复版本。目前已修复的版本包括 OceanBase 数据库 V3.2.3 BP10(oceanbase-3.2.3.3-110000092023091219)、V3.2.4 BP5(oceanbase-3.2.4.5-105000012023081513)。
规避方式
如果业务允许,可以在事务最开始执行一条普通(未带 trigger、外键等)DML 语句规避。