根本原因是快速刷新需全量扫描MLOG$日志表,无索引+统计信息过期导致全表扫描与磁盘排序;应建(snaptime$$,sequence$$)复合索引、gather_table_stats全采样、定期purge日志,并在大分区刷新时启用atomic_refresh=FALSE以truncate+append提速。
物化视图刷新卡在MLOG$扫描阶段,怎么破
根本原因是快速刷新要全量扫描物化视图日志表(如 mlog$_member_order),而日志条目超百万后,没索引+统计信息过期=全表扫描+磁盘排序,直接卡死。
实操建议:
- 立刻建复合索引:
CREATE INDEX idx_mlog_seq ON MLOG$_MEMBER_ORDER (snaptime$$, sequence$$);索引字段顺序不能颠倒,snaptime$$必须在前 - 马上更新统计信息:
EXEC DBMS_STATS.GATHER_TABLE_STATS(user, 'MLOG$_MEMBER_ORDER');别信默认采样率,用estimate_percent => 100更稳 - 查日志积压:
SELECT COUNT(*) FROM MLOG$_MEMBER_ORDER WHERE snaptime$$ ;如果结果很大,执行 <code>EXEC DBMS_MVIEW.PURGE_LOG('MEMBER_ORDER', 1)清理1天前的记录
一刷就锁表,v$locked_object 显示基表被占,怎么办
不是“刷得慢”,是刷的时候和业务写入撞上了——物化视图快速刷新会持基表或日志表的行级锁,而大事务 UPDATE 正在改同一张分区表,形成阻塞链。
先定位真凶:
- 查谁在锁:
SELECT object_name, locked_mode FROM v$locked_object lo, dba_objects ao WHERE lo.object_id = ao.object_id;重点盯MLOG$_*和主表名 - 顺藤摸瓜:
SELECT sid, serial#, sql_id, event, blocking_session FROM v$session WHERE sid IN (SELECT session_id FROM v$locked_object);看blocking_session是不是指向一个长运行的存储过程(哪怕它不碰物化视图) - 别急着 kill;先确认那个会话是否还在干活:
SELECT last_call_et FROM v$session WHERE sid = <sid>;如果 > 3600 秒且status = 'INACTIVE',大概率可安全中止
ATOMIC_REFRESH=FALSE 到底要不要开
要开,但只在大分区刷新时开。默认 TRUE 会让整个刷新包在一个事务里:TRUNCATE 不允许、INSERT 要记 UNDO、全程锁表——对千万级数据,undo 表空间撑爆、锁表时间超长,基本等于自废武功。
开的好处和代价:
- 开之后,Oracle 改用
TRUNCATE + INSERT /*+ APPEND */,跳过事务日志,锁粒度降到分区级,速度提升3–5倍 - 副作用是刷新过程中物化视图短暂为空;应用层若没做双读或降级逻辑,查询会返回空结果
- 定时任务里必须显式传参:
DBMS_MVIEW.REFRESH(list => 'MV_SPMEMBERORDER', atomic_refresh => FALSE);DBMS_JOB 默认值不管用
为什么查不到锁,但刷新就是不动
常见于跨库场景(比如主表在 Oracle 10g,物化视图建在 19c,走 DBLink 同步),或物化视图定义含隐式约束(如 SELECT 中用了子查询未加 NO_MERGE 提示),导致优化器无法生成增量刷新计划,自动退化为全量刷新+隐式锁等待。
排查重点:
- 查刷新方式是否真为 FAST:
SELECT refresh_method, rewrite_enabled FROM user_mviews WHERE mview_name = 'MV_SPMEMBERORDER';如果refresh_method是COMPLETE,说明快速刷新已失效 - 看日志是否启用必要字段:
SELECT master, log_table, rowids, sequence FROM user_mview_logs WHERE log_table = 'MLOG$_MEMBER_ORDER';rowids和sequence必须为YES - 检查 MV 定义是否含禁止 FAST 的结构:
SELECT query_text FROM user_mviews WHERE mview_name = 'MV_SPMEMBERORDER';含ROWNUM、聚合函数、多表 JOIN 且无合适连接键,都会让 FAST 失效
真正麻烦的点不在“怎么刷”,而在“谁在刷”和“刷之前有没有人动过日志表”。很多卡住不是因为 SQL 写得差,而是 snaptime$$ 精度丢失、DBLink 连接超时残留事务、甚至另一个作业偷偷 truncate 了日志表——这些细节不翻 v$session_wait 和 SQL_TEXT 根本看不到。


















