物化视图查询重写不走新索引,是因为优化器未识别到索引——根本原因是MV统计信息未更新、QUERY_REWRITE_ENABLED或MV的REWRITE_ENABLED未启用、索引列顺序/表达式不匹配、GROUP BY列不在索引最左前缀,或谓词下推失败导致全扫。
物化视图查询重写不走新索引,是因为优化器根本没“看见”它
oracle 查询重写(query rewrite)发生在语义等价替换阶段,不关心底层索引是否存在或是否最新——它只判断:有没有启用重写、mv 是否匹配、谓词能否下推。哪怕你刚在物化视图上 create index 了一个完美覆盖的本地索引,只要重写后的执行计划里 object_name 指向的是 mv 名,而该 mv 的统计信息仍是旧的(比如 dbms_stats.gather_table_stats 没跑过),优化器就会按“无索引”估算成本,直接放弃使用。
常见错误现象:
- 执行
EXPLAIN PLAN FOR SELECT ...后查PLAN_TABLE,看到OPERATION = TABLE ACCESS FULL或INDEX FAST FULL SCAN,但明明建了LOCAL索引 -
USER_INDEXES里索引状态是VALID,USER_TAB_STATISTICS里NUM_ROWS却为 0 或远低于实际
实操建议:
- 刷新物化视图后必须立刻收集统计信息:
EXEC DBMS_STATS.GATHER_TABLE_STATS(ownname => 'SCHEMA', tabname => 'MV_SALES', granularity => 'PARTITION')(注意加granularity => 'PARTITION',否则分区级统计为空) - 确认索引列顺序与查询中
WHERE+GROUP BY+ORDER BY的字段顺序严格一致;例如查询是WHERE region = ? AND dt >= ? GROUP BY product_type,索引就得是(region, dt, product_type),不能颠倒 - 避免在索引列上用函数:如物化视图定义含
TRUNC(sales_date),索引也必须建在TRUNC(sales_date)表达式上,而非原始列
QUERY REWRITE_ENABLED 和 MV 的 ENABLE QUERY REWRITE 必须同时生效
只要其中任一开关关闭,查询重写就不会触发,自然谈不上“选不选索引”。这不是性能问题,而是功能未启用。
检查方式:
- 查会话级设置:
SELECT value FROM v$parameter WHERE name = 'query_rewrite_enabled',值必须是TRUE - 查物化视图本身:
SELECT rewrite_enabled FROM user_mviews WHERE mview_name = 'MV_SALES',值必须是ENABLED,不是DISABLED或空
容易踩的坑:
- DBA 修改了
QUERY_REWRITE_ENABLED=FALSE作为全局策略,但没通知开发,导致所有重写失效 - 物化视图建好后忘了加
ENABLE QUERY REWRITE选项,或者建完后被ALTER MATERIALIZED VIEW ... DISABLE QUERY REWRITE关闭过 - 用户连接时用了
ALTER SESSION SET QUERY_REWRITE_ENABLED = FALSE,会话级覆盖全局设置
聚合查询中索引失效的典型场景:GROUP BY 列不在索引最左前缀
Oracle 本地索引对聚合查询生效的前提,是索引能支撑过滤 + 分组两个动作。如果索引是 (customer_id, sales_amt),但查询是 GROUP BY product_type,那这个索引完全无法用于分组排序,优化器宁可全扫也不会用。
关键点:
- 聚合查询的
GROUP BY列必须出现在索引最左位置,或至少是索引前缀的一部分;例如要支持GROUP BY region, product_type,索引至少得是(region, product_type, ...) - 若查询含
HAVING COUNT(*) > 100,且COUNT(*)来自物化视图预聚合字段(如cnt),索引必须包含该字段才能避免回表 - 物化视图定义中若用了
SUM(amount) AS total_sales,索引不能建在total_sales上(这是表达式列),而应建在基列amount或预计算列(需先ADD COLUMN)
为什么禁用重写(NOREWRITE)后反而走索引了
这恰恰说明问题不在索引本身,而在重写逻辑破坏了谓词下推路径。例如原始 SQL 有 WHERE dt BETWEEN DATE'2025-01-01' AND DATE'2025-01-31',但重写后生成的对 MV 的访问语句里,这个条件被优化器“吸收”进聚合逻辑,没传给扫描层,导致 MV 全分区扫描。
验证方法:
- 加 hint 强制不重写:
SELECT /*+ NOREWRITE */ SUM(sales) FROM fact_sales WHERE dt BETWEEN ...,看执行计划是否回到原表 + 索引扫描 - 对比重写前后谓词:查
PLAN_TABLE中PREDICATE_INFORMATION字段,确认重写后的ACCESS_PREDICATES是否包含分区键条件 - 临时把 MV 改成非分区表再测试——如果这时索引突然生效,基本锁定是分区裁剪未继承的问题
真正难处理的是谓词下推失败的边界情况:比如 MV 含 JOIN,而时间条件来自右表,Oracle 11g 的重写器大概率不敢下推,只能全扫。这种时候,与其强求索引,不如重构 MV 定义,把过滤条件提前到驱动表侧。


















