物化视图日志必须显式指定WITH ROWID或WITH PRIMARY KEY,不可省略;选哪个取决于源表主键是否稳定可用:有启用且不被更新的主键则用WITH PRIMARY KEY,否则必须用WITH ROWID,且日志类型须与物化视图声明严格匹配,否则FAST刷新失败。

物化视图日志必须用ROWID还是PRIMARY KEY?
Oracle要求物化视图日志必须显式指定WITH ROWID或WITH PRIMARY KEY,不能省略。选哪个不取决于“习惯”或“默认”,而取决于源表是否具备稳定、可用的主键:
-
WITH PRIMARY KEY:源表必须有定义且启用的主键约束(非空+唯一+不可更新),且该主键在所有刷新周期内保持不变 -
WITH ROWID:源表无主键、主键被禁用、或主键列可能被UPDATE(如业务上允许改ID)时的兜底方案 - 两者不能混用:日志类型必须与后续物化视图创建时的
WITH PRIMARY KEY/WITH ROWID声明严格匹配,否则REFRESH FAST会失败
常见翻车点:CREATE MATERIALIZED VIEW LOG ON t;不带WITH子句,直接报ORA-12014;或者日志用了WITH PRIMARY KEY,但物化视图写成WITH ROWID,刷新时报ORA-12008(刷新路径不匹配)。
为什么主键模式下物化视图日志还可能失效?
主键看似稳定,但在实际运维中容易被破坏:
- 主键约束被
DISABLE VALIDATE或DROP后,日志仍存在,但REFRESH FAST立即报错ORA-12008 - 源表执行
ALTER TABLE ... MOVE或SHRINK SPACE,主键索引重建导致部分行ROWID变更,但主键值未变——此时日志记录的旧主键映射关系仍有效,不影响FAST刷新(这是主键模式的优势) - 但如果主键列本身被UPDATE(例如
UPDATE t SET id = id + 100 WHERE ...),哪怕只改一行,该行在日志中就变成“新插入+旧行删除”,增量逻辑仍能处理,但需确保物化视图查询语句里没对主键做函数包装(如WHERE UPPER(id) = 'X'),否则无法走FAST
关键判断:只要主键列值不被业务逻辑修改,且约束始终ENABLED,WITH PRIMARY KEY就是更安全的选择;否则一律切到WITH ROWID。
ROWID模式下哪些操作会导致FAST刷新失败?
WITH ROWID日志依赖物理位置稳定性,以下操作会直接让增量刷新退化为COMPLETE甚至报错:
- 源表执行
ALTER TABLE ... MOVE、ALTER TABLE ... SHRINK SPACE、ALTER TABLE ... COMPRESS→ 所有旧ROWID失效,下次REFRESH FAST报ORA-12008 - 分区表执行
SPLIT/EXCHANGE分区 → 被移动分区内的ROWID批量变更,日志无法定位原行 - 使用
INSERT /<em>+ APPEND </em>/大批量加载 → 新数据块分配不连续,ROWID跳跃,但不影响日志捕获,仅影响后续基于ROWID范围扫描的分批逻辑(如自定义JOB切片) - 远程表(通过DBLink)创建日志时未在远端用户上授予
SELECT权限 → 日志能建,但刷新时查不到远端ROWID变化,静默降级为全量
注意:WITH ROWID日志本身不存储主键,所以物化视图查询里不能含聚合、DISTINCT、JOIN或多表子查询,否则Oracle拒绝创建FAST刷新能力——这是语法硬限制,不是性能问题。
金仓数据库(KingbaseES)里ROWID日志能不能直接照搬Oracle写法?
不能直接照搬,必须确认default_with_rowid配置状态:
- 若未开启
default_with_rowid = true,建表时不生成OID,SELECT rowid FROM t会报错,自然也无法创建WITH ROWID日志 - 即使开启,金仓的ROWID本质是OID映射,删除后OID可能被重用,因此基于ROWID的增量刷新在长期运行中存在“误匹配”风险(比如某行删了,新行恰好拿到相同OID)
- 金仓要求
WITH ROWID日志必须搭配INCLUDING NEW VALUES,否则REFRESH FAST不识别UPDATE操作,只捕获INSERT/DELETE - Oracle允许
CREATE MATERIALIZED VIEW LOG ON t WITH ROWID;,金仓必须写成CREATE MATERIALIZED VIEW LOG ON t WITH ROWID, SEQUENCE (col1, col2) INCLUDING NEW VALUES;,少任一子句都创建失败
最稳妥做法:迁移前用SHOW default_with_rowid;确认开关,再检查源表是否已含OID(\d t看是否有oid列),别假设“和Oracle一样就行”。


















