物化视图日志暴增主因是日志未被消费,而非DML频繁;Oracle仅在FAST刷新成功后按SNAPTIME$$清理日志,长期未刷新或刷新失败会导致日志只增不减,超10万行且MIN(snaptime$$)远早于当前时间即可判定消费停滞。
物化视图日志行数暴增,根本不是DML太频繁
日志表 mlog$_xxx 行数飙升,90% 以上情况和 dml 频率无关,而是因为日志没被消费——物化视图长期没刷新,或刷新失败后没人处理。oracle 只在成功完成一次 fast 刷新后,才根据 snaptime$$ 安全清理旧记录;一旦卡住,日志就只进不出。
验证是否已失控:
-
SELECT COUNT(*) FROM MLOG$_YOUR_TABLE_NAME;—— 超过 10 万且持续上涨,基本可判定消费停滞 -
SELECT MIN(snaptime$$) FROM MLOG$_YOUR_TABLE_NAME;—— 若返回时间远早于当前(如 2025 年),说明最早那批变更至今未被任何 MV 拉走 -
SELECT mview_name, last_refresh_date FROM dba_mviews WHERE master_link IS NULL AND refresh_method = 'FAST';—— 找出所有依赖该基表的 FAST MV,看它们的last_refresh_date是否全部严重滞后
为什么“刷新任务在跑”,日志还是不减?
常见假象:DBA 看到 dba_scheduler_jobs 里状态是 RUNNING 或 SUCCEEDED,就以为日志在清。其实很多刷新作业失败后不报错、不重试、也不更新 last_refresh_date,只是静默降级为 COMPLETE,而 COMPLETE 刷新根本不读日志,自然也不会触发日志清理。
必须查真实执行痕迹:
-
SELECT * FROM dba_mview_refresh_times WHERE mview_name = 'YOUR_MV' AND refresh_date > SYSDATE-1;—— 确认最近一次是否真执行了,且类型是F(FAST) -
SELECT * FROM dba_jobs WHERE what LIKE '%DBMS_MVIEW.REFRESH%';或SELECT * FROM dba_scheduler_jobs WHERE job_action LIKE '%DBMS_MVIEW.REFRESH%';—— 查作业状态是否为BROKEN,这类作业不会自动恢复 -
SELECT log_table, oldest_snapshot FROM dba_mview_logs WHERE master = 'YOUR_TABLE_NAME';—— 对比oldest_snapshot和对应 MV 的last_refresh_date,若前者更老,说明日志堆积已发生
高并发 DML 下,日志写入本身就在拖慢基表性能
物化视图日志表默认继承基表所在表空间的 LOGGING 属性。每条 INSERT 进 MLOG$_xxx 都生成 REDO,高频 DML 场景下,日志写入可能成为基表 DML 的瓶颈,形成“DML 慢 → 刷新更慢 → 日志更胀 → DML 更慢”的死循环。
缓解动作要立刻做:
- 紧急时可临时关闭日志写 REDO:
ALTER TABLE MLOG$_XXX NOLOGGING;(注意:之后必须尽快完成一次成功FAST刷新,否则丢失的变更不可追) - 检查日志表是否建在高性能存储上;避免和基表共用热区数据文件
- 确认是否真需要所有字段都进日志——如果 MV 只查
ID, NAME,建日志时就别用INCLUDING NEW VALUES,改用WITH PRIMARY KEY+ 显式列列表
DBMS_MVIEW.PURGE_LOG 不是万能的,参数含义容易误解
DBMS_MVIEW.PURGE_LOG 的 num_days 参数,不是按系统时间删,而是按日志里每条记录的 SNAPTIME$$ 字段值来判断。如果某条记录的 SNAPTIME$$ 是 2025-03-01,哪怕现在是 2026-04-29,只要 num_days => 30,它就不会被删。
所以实际清理前必须确认:
- 先查
SELECT MIN(snaptime$$), MAX(snaptime$$) FROM MLOG$_XXX; - 再决定
num_days值,例如最大值是 2026-04-20,则设num_days => 1就能清掉 2026-04-19 之前的所有记录 - 不要对正在被 MV 依赖的日志直接
TRUNCATE—— 必须先停掉所有关联刷新,或确保后续能立即跑通一次完整FAST刷新,否则增量变更永久丢失
最常被忽略的一点:PURGE_LOG 不会清理 CHANGE_VECTOR$$ 或 M_ROW$$ 字段里的二进制内容,这些字段体积大、压缩率低,真正占空间的往往是它们。如果日志已严重膨胀,光靠 PURGE_LOG 可能收效甚微,得结合重建日志或 MV 本身。











