物化视图必须显式分区且查询条件直接作用于其物理分区列,同时需收集ALL粒度统计信息,三者缺一不可。否则无法触发PARTITION RANGE裁剪,执行计划将退化为FULL SCAN或走基表。

物化视图必须显式分区,否则根本不会出现 PARTITION RANGE 节点
Oracle 不会自动把基表的分区结构“继承”到物化视图上。哪怕基表是按 sale_date RANGE 分区的,物化视图默认仍是单一分区段(single segment),查询时执行计划里只会看到 TABLE ACCESS FULL,绝不会有 PARTITION RANGE SINGLE 或 ITERATOR。
解决办法只有一条:建物化视图时必须用 PARTITION BY RANGE 显式声明分区策略,且分区键列必须和基表物理分区列完全一致(不是 SELECT 列里的别名或函数结果)。
- 正确写法:
CREATE MATERIALIZED VIEW mv_sales PARTITION BY RANGE (sale_date) AS SELECT sale_date, region, SUM(amount) FROM sales GROUP BY sale_date, region; - 错误写法:
SELECT TRUNC(sale_date, 'MM') AS month_key ...—— 这会让优化器无法关联到原始分区键 - 若基表是本地复制表(如
sales_remote_copy),确保它本身已分区,且物化视图不带@dblink,否则整个访问路径退化为REMOTE,分区信息彻底丢失
WHERE 条件必须直接作用于物化视图的物理分区列
即使物化视图已按 sale_date 分区,查询中也不能用任何包装函数——TRUNC(sale_date)、EXTRACT(YEAR FROM sale_date)、TO_CHAR(sale_date, 'YYYY-MM') 全部无效。优化器无法证明这些表达式与原始分区键等价,裁剪逻辑直接失效。
典型错误现象:执行计划里 PARTITION RANGE 节点出现了,但 OBJECT_NAME 列显示的是基表名(如 SALES),不是物化视图名(如 MV_SALES)——说明查询根本没走重写,只是基表自己被裁剪了。
- 必须写:
WHERE sale_date >= DATE '2026-01-01' - 不能写:
WHERE TO_CHAR(sale_date, 'YYYY-MM') = '2026-01' - 检查实际分区列名:
SELECT COLUMN_NAME FROM USER_TAB_PARTITIONS WHERE TABLE_NAME = 'MV_SALES' AND ROWNUM = 1(注意:这里返回的是物理列,不是 SELECT 列表里的别名)
刷新方式和日志配置不当,会导致分区裁剪在 FAST REFRESH 时失效
物化视图能支持分区裁剪,不等于每次刷新都能利用它。如果基表未分区,或物化视图日志缺失关键列,FAST REFRESH 就会退化为全量扫描,性能断崖式下跌。
常见疏漏点集中在日志定义上:必须包含 ROWID、分区键列(如 sale_date),且需 INCLUDING NEW VALUES;否则 ON COMMIT 刷新失败,或报 ORA-12032。
- 正确日志:
CREATE MATERIALIZED VIEW LOG ON sales WITH ROWID, SEQUENCE(sale_date) INCLUDING NEW VALUES; - 错误日志:
WITH ROWID但没加SEQUENCE(sale_date)→ 刷新时无法定位变更发生在哪个分区 - 基表是大宽表且未按时间分区?那无论物化视图怎么分,每次 FAST REFRESH 都得扫全量,
mv_refresh_time会随数据增长线性飙升
统计信息不同步,CBO 会误判成本并绕过物化视图
执行计划显示走基表而非物化视图,但 DBMS_MVIEW.EXPLAIN_REWRITE 又说可重写?这大概率是 CBO 成本估算失真——尤其当物化视图刚刷新完却没收集统计信息时,优化器会认为查 MV 比扫基表更贵。
必须对物化视图执行完整统计信息采集,且指定 GRANULARITY => 'ALL',否则默认只收集全局统计,各分区内部数据分布完全不可见。
- 强制收集:
DBMS_STATS.GATHER_TABLE_STATS(ownname => 'SCOTT', tabname => 'MV_SALES', granularity => 'ALL'); - 验证同步性:
SELECT TABLE_NAME, STALE_STATS, LAST_ANALYZED FROM USER_TAB_STATISTICS WHERE TABLE_NAME IN ('SALES', 'MV_SALES'); - 临时验证是否重写生效:
SELECT /*+ REWRITE(MV_SALES) */ COUNT(*) FROM sales WHERE sale_date >= DATE '2026-01-01';—— 如果加 hint 后执行计划立刻切到 MV 且 pstart/pstop 正常,就坐实是成本误判


















