Duality View 这玩意儿,本质就是给关系表套了个 JSON 马甲

26ai 推了个 JSON Relational Duality Views,说是"关系型和文档型的完美融合"。我第一反应是:这不就是物化视图加了个 JSON 序列化吗?扒了扒数据字典,发现事情没那么简单。

2.1 ETAG 版本控制是怎么实现的

Duality View 的核心是 ETAG。当你通过 Duality View 更新 JSON 文档时,Oracle 会在后台做两件事:第一,把 JSON Patch(RFC 6902)翻译成对底层关系表的 DML;第二,用 ORA_ROWSCN 生成 ETAG,保证并发修改时不会互相覆盖。这跟 HTTP 的 ETag/If-Match 机制一模一样。

但这里有个坑:如果底层关系表有触发器(Trigger),Duality View 的更新路径会绕开部分行级触发器。我们在一个客户现场就踩了这坑——底层表有个 BEFORE UPDATE 触发器做审计日志,结果通过 Duality View 更新 JSON 时,触发器没触发,审计断了。MOS 上说是"by design",因为 Duality View 走的是内部生成的 INSTEAD OF 触发器路径。解决办法是改用 COMPOUND TRIGGER 或者把审计逻辑挪到 FGA 里。

2.2 模拟实验:并发更新的一致性

-- 底层关系表
CREATE TABLE employees_base (
    emp_id NUMBER PRIMARY KEY,
    emp_name VARCHAR2(100),
    salary NUMBER,
    dept_id NUMBER REFERENCES departments(dept_id)
);

-- Duality View
CREATE JSON RELATIONAL DUALITY VIEW emp_dv AS
SELECT JSON {
    'id'    : e.emp_id,
    'name'  : e.emp_name,
    'salary': e.salary,
    'dept'  : (SELECT JSON {'dept_id': d.dept_id, 'dept_name': d.dept_name}
               FROM departments d WHERE d.dept_id = e.dept_id)
}
FROM employees_base e;

-- 会话 A 读取 ETAG
SELECT JSON_SERIALIZE(data RETURNING CLOB PRETTY)
FROM emp_dv WHERE JSON_VALUE(data, '$.id') = 1;
-- 假设返回 ETAG: W/"2026-04-15T08:30:00.123456"

-- 会话 B 同时更新
UPDATE emp_dv
SET data = JSON_MERGEPATCH(data, '{"salary": 20000}')
WHERE JSON_VALUE(data, '$.id') = 1;

-- 会话 A 用旧 ETAG 更新
UPDATE emp_dv
SET data = JSON_MERGEPATCH(data, '{"salary": 25000}')
WHERE JSON_VALUE(data, '$.id') = 1
  AND ORA_ROWSCN = TIMESTAMP_TO_SCN(TO_TIMESTAMP('2026-04-15T08:30:00.123456', 'YYYY-MM-DD"T"HH24:MI:SS.FF6'));
-- 结果:0 行更新,因为 ETAG 已过期

这个实验验证了 Duality View 的乐观锁机制。但注意,ETAG 是基于 SCN 的,如果数据库开了 Flashback Data Archive,SCN 的时间映射会有额外开销。我们在一个开了 FDA 的库上测过,Duality View 的更新延迟比普通表高 15% 左右。

总结:Duality View 适合那种"读多写少、需要文档型接口"的场景,比如电商商品详情、合同模板。如果是高频 OLTP 交易,老老实实走关系表,别折腾这层封装。


请使用浏览器的分享功能分享到微信等