概念描述
谓词推入(Pushing Predicate):当 SQL 语句中包含不能合并的视图,同时视图有谓词过滤(也就是 where 过滤条件), CBO 会将谓词过滤条件推入视图中,这个过程就叫作谓词推入。
谓词推入的主要目的就是让 Oracle 尽可能早地过滤掉无用的数据,从而提升查询性能。
为什么谓词推入必须要有不能被合并的视图呢?因为一旦视图被合并了,执行计划中根本找不到视图,这个时候谓词往哪里推呢?所以谓词推入的必要前提是 SQL 中要有不能合并的视图。
测试验证
我们先创建一个不能被合并的视图(视图中有 union all)。
create or replace view v_pushpredicate as
select * from test
union all
select * from test where rownum>=1;
查看执行计划
select * from v_pushpredicate where object_id<10;
SQL 语句中, where 过滤条件是针对视图过滤的,但是从执行计划中(Id=4)我们可以看到, where 过滤条件跑到视图中的表中进行过滤了,这就是谓词推入。因为视图中第二个表有rownum, rownum 会阻止谓词推入,所以第二个表走的是全表扫描,需要到视图上进行过滤(Id=1)。
如果 VIEW 前面有“*”号,这就说明有谓词没有推入到视图中。
还有一种谓词推入,是把连接列当作谓词推入到视图中,这种谓词推入我们一般叫作连接列谓词推入,此类谓词推入最容易产生性能问题。
create or replace view v_pushpredicate as
select * from test
union all
select * from test;
添加 HINT:push_pred 提示将连接列推入到视图中
select /*+ push_pred(b) */ *
from test a, v_pushpredicate b
where a.object_id = b.object_id
and a.owner = 'SCOTT';
将连接列推入到视图中这种谓词推入,一般在执行计划中都能看到 PUSHED PREDICATE或者 VIEW PUSHED PREDICATE,而且视图一般作为嵌套循环的被驱动表,同时视图中谓词被推入列有索引。这种谓词推入对性能有好有坏。为什么连接列谓词推入,被推入的视图一般都作为嵌套循环的被驱动表呢?这是因为连接列谓词推入需要传值(传值到视图里面),而有传值操作的表连接方法只有嵌套循环或者 FILTER。 FILTER 是专门针对半连接或者反连接的(where 后面的子查询),谓词推入是专门针对 from 后面的子查询,所以连接列谓词推入,被推入的视图一般都作为嵌套循环的被驱动表。
去掉 HINT:push_pred
select *
from test a, v_pushpredicate b
where a.object_id = b.object_id
and a.owner = 'SCOTT';
如果不将连接列推入到视图中,视图里面的表就只能全表扫描,这时性能远不如索引扫描。
知识总结
如果遇到执行计划中 VIEW PUSHED PREDICATE 一定要注意,如果 SQL 执行很快,不用理会;如果 SQL 执行很慢,可以先关闭连接列谓词推入(alter session set “_push_join_predicate” = false)功能,再逐步分析为什么连接列谓词推入之后, SQL 性能很差。连接列谓词推入性能变差一般是CBO 将驱动表 Rows 计算错误(算少),导致视图作为嵌套循环被驱动表,然后一直反复被扫描;也有可能是视图太过复杂,视图本身存在性能问题,这时需要单独优化视图。例如视图单独执行耗时 1 秒,在进行谓词推入之后,视图会被扫描多次,假设扫描 1 000 次,每次执行时间从 1 秒提升到了 0.5 秒,但是视图被执行了 1 000 次,总的耗时反而多了,这时谓词推入反而降低性能。
一定要注意,当视图中有 rownum 会导致无法谓词推入,所以一般情况下,我们不建议在视图中使用 rownum。为什么 rownum 会导致无法谓词推入呢?这是因为当谓词推入之后,rownum 的值已经发生改变,已经改变了 SQL 结果集,任何查询变换必须是在不改变 SQL 结果集的前提下才能进行。