merge 和 push_pred

转自 回帖 http://www.itpub.net/thread-1306866-1-1.html

首先,我们看看 Jonathan Lewis 的解释:

•        Merge / no_merge: if you use a complex view (e.g. aggregate view, or join view) in your query, should you rewrite the query to merge the tables in the view into a single from clause with all the other tables (merge), or should you evaluate the view to produce a “standalone” result set and then join the result set to the remaining tables (no_merge). 
-- 只要查询中出现view,就可以使用merge/no_merge;
merge 的含义是把veiw展开,把sql从(view join view/table) 变成多个table 相互jion的写法, 执行计划中也不会出现view的acces path~
no_merge 则正好相反,不改写view,不把view展开~ 执行计划中必定出现view的access path~
•        Push_pred / no_push_pred: If you have a non-mergeable view (possible because of a no_merge hint) in your query, how should you operate the join from other tables; should you create one large view result and join it once (no_push_pred) or should you push the join predicate down into the view definition and recreate the view result set for every driving row from another table (push_pred).
--同样查询中必须至少有view的存在,并且这个view是不可以被merge的情况下才可以使用push_pred/no_push_pred
no_push_pred的含义是view只作为一个view先得出结果,然后根据连接条件和其他的table/view根据连接条件,进行连接~
Push_pred的含义是我将“连接条件”插入到view中, 使用其他结果集根据连接条件去驱动这个view进行连接(也就是被push_pred的view只能作为nested-loop的外循环)

下面是我的一个测试
create table tx (x1 int,x2 int);
create table ty (y1 int,y2 int);
create index ind1_tx on tx (x1);
create index ind2_tx on tx (x2);
create index ind1_ty on ty (y1);
create index ind2_ty on ty (y2);
insert into tx select rownum+1,rownum+3 from dba_tables;
insert into ty select rownum+2,rownum+4 from dba_tables;
commit;
exec dbms_stats.gather_table_stats(user,'TX',cascade=>true);
exec dbms_stats.gather_table_stats(user,'TY',cascade=>true);

查询使用的是:
select * from tx, (select ty.y2 from ty where ty.y1 <10) y  
where tx.x1 <10 and tx.x2 =y.y2(+)

--也就是table tx 和 view y通过连接条件tx.x2=y.y2(+) 进行外连接 (因为我发现似乎push_pred只能发生在外连接上~)

先看看merge/no_merge
PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------

SQL_ID  asavnptg692v7, child number 1
-------------------------------------
select /*+ merge(y) */ * from tx, (select ty.y2 from ty where ty.y1 <10) y  where tx.x1
<10 and tx.x2 =y.y2(+)

Plan hash value: 161655348

--------------------------------------------------------------------------------------------------
| Id  | Operation                    | Name    | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
--------------------------------------------------------------------------------------------------
|*  1 |  HASH JOIN OUTER             |         |      1 |      8 |      8 |00:00:00.01 |       8 |
|   2 |   TABLE ACCESS BY INDEX ROWID| TX      |      1 |      8 |      8 |00:00:00.01 |       3 |
|*  3 |    INDEX RANGE SCAN          | IND1_TX |      1 |      8 |      8 |00:00:00.01 |       2 |
|   4 |   TABLE ACCESS BY INDEX ROWID| TY      |      1 |      7 |      7 |00:00:00.01 |       5 |
|*  5 |    INDEX RANGE SCAN          | IND1_TY |      1 |      7 |      7 |00:00:00.01 |       3 |
--------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - access("TX"."X2"="TY"."Y2"
   3 - access("TX"."X1"<10)
   5 - access("TY"."Y1"<10)




PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------

SQL_ID  2jybp392j7vab, child number 1
-------------------------------------
select /*+ no_merge(y) */ * from tx, (select ty.y2 from ty where ty.y1 <10) y  where tx.x1
<10 and tx.x2 =y.y2(+)

Plan hash value: 453677432

---------------------------------------------------------------------------------------------------
| Id  | Operation                     | Name    | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
---------------------------------------------------------------------------------------------------
|*  1 |  HASH JOIN OUTER              |         |      1 |      8 |      8 |00:00:00.01 |       8 |
|   2 |   TABLE ACCESS BY INDEX ROWID | TX      |      1 |      8 |      8 |00:00:00.01 |       3 |
|*  3 |    INDEX RANGE SCAN           | IND1_TX |      1 |      8 |      8 |00:00:00.01 |       2 |
|   4 |   VIEW                        |         |      1 |      7 |      7 |00:00:00.01 |       5 |
|   5 |    TABLE ACCESS BY INDEX ROWID| TY      |      1 |      7 |      7 |00:00:00.01 |       5 |
|*  6 |     INDEX RANGE SCAN          | IND1_TY |      1 |      7 |      7 |00:00:00.01 |       3 |
---------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - access("TX"."X2"="Y"."Y2"
   3 - access("TX"."X1"<10)
   6 - access("TY"."Y1"<10)

--可见虽然执行计划没有什么改变,但是当我们使用no_merge的时候 view y出现在了access path中,也就是说第二个执行计划(no_merge)中是table tx和view y做的
hash join outer ,而不是像第一个执行计划中ty可以直接和tx发生hash join outer~ 也就是no_merge的条件下必须先计算view的结果,然后才能和其它的
table或者view或者结果集,进行连接~


然后我们再看看push_pred/no_push_pred (请注意这两个hints生效的前提是sql必须有view而且这个view不能被merge,所以我使用的是no_merge来保证这点)

PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------

SQL_ID  c9j3pyyhrhcr6, child number 0
-------------------------------------
select /*+ no_merge(y) no_push_pred(y) */ * from tx, (select ty.y2 from ty where ty.y1
<10) y  where tx.x1 <10 and tx.x2 =y.y2(+)

Plan hash value: 453677432

---------------------------------------------------------------------------------------------------
| Id  | Operation                     | Name    | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
---------------------------------------------------------------------------------------------------
|*  1 |  HASH JOIN OUTER              |         |      1 |      8 |      8 |00:00:00.01 |       8 |
|   2 |   TABLE ACCESS BY INDEX ROWID | TX      |      1 |      8 |      8 |00:00:00.01 |       3 |
|*  3 |    INDEX RANGE SCAN           | IND1_TX |      1 |      8 |      8 |00:00:00.01 |       2 |
|   4 |   VIEW                        |         |      1 |      7 |      7 |00:00:00.01 |       5 |
|   5 |    TABLE ACCESS BY INDEX ROWID| TY      |      1 |      7 |      7 |00:00:00.01 |       5 |
|*  6 |     INDEX RANGE SCAN          | IND1_TY |      1 |      7 |      7 |00:00:00.01 |       3 |
---------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - access("TX"."X2"="Y"."Y2"
   3 - access("TX"."X1"<10)
   6 - access("TY"."Y1"<10)



PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------

SQL_ID  6hxjk8xpzn3fm, child number 1
-------------------------------------
select /*+ no_merge(y) push_pred(y) */ * from tx, (select ty.y2 from ty where ty.y1 <10) y
where tx.x1 <10 and tx.x2 =y.y2(+)

Plan hash value: 1364836571

---------------------------------------------------------------------------------------------------
| Id  | Operation                     | Name    | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
---------------------------------------------------------------------------------------------------
|   1 |  NESTED LOOPS OUTER           |         |      1 |      8 |      8 |00:00:00.01 |      22 |
|   2 |   TABLE ACCESS BY INDEX ROWID | TX      |      1 |      8 |      8 |00:00:00.01 |       5 |
|*  3 |    INDEX RANGE SCAN           | IND1_TX |      1 |      8 |      8 |00:00:00.01 |       3 |
|   4 |   VIEW PUSHED PREDICATE       |         |      8 |      1 |      7 |00:00:00.01 |      17 |
|*  5 |    TABLE ACCESS BY INDEX ROWID| TY      |      8 |      1 |      7 |00:00:00.01 |      17 |
|*  6 |     INDEX RANGE SCAN          | IND2_TY |      8 |      1 |      7 |00:00:00.01 |      10 |
---------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   3 - access("TX"."X1"<10)
   5 - filter("TY"."Y1"<10)
   6 - access("TY"."Y2"="TX"."X2"

-- 可见当我使用no_push_pred的时候view可以先得出结果集,然后和其它的table或者view或者结果集进行hash join outer连接~ 也就是连接条件并不发生在view的计算中~
但是当我使用push_pred的时候,tx的结果集开始使用连接条件驱动view y,进行nested loop outer操作~ 来完成连接~ 注意出现了 VIEW PUSHED PREDICATE,请注意ty的索引使用和access 谓词~ 
no_push_pred的时候view y 的计算只是用了6 - access("TY"."Y1"<10)来访问ind1_ty,
而使用push_pred的时候view y的计算使用的是6 - access("TY"."Y2"="TX"."X2"来访问 IND2_TY 并且通过5 - filter("TY"."Y1"<10)来过滤ty.y1<10这个过滤条件,
也就是说push_pred是把连接条件tx.x2=y.y2推入到了view中,变为tx.x2=ty.y2来先进行计算~


那么我们可以看出merge和push_pred的区别了~

merge是把view中的table提取出来,重写sql,将view的连接变成table的连接
而push_pred必须保证有view不能merge~ 也就是说view还是一个整体,只不过连接条件要在view中先进性计算~

这也就决定了merge可以使用table之间的hash jion或者nest loop来完成
但是push_pred只能使用nested loop的方式来被驱动完成sql地执行~

我想这就是他们的区别吧~
所以其实我觉得/no_merge no_push_pred/和/no_merge push_pred/的选择,关键在于使用nestloop方式连接这个view是否能够带来性能的提升~

push_pred 对应的执行计划operation是 VIEW PUSHED PREDICATE

只有在执行计划里出现了这个operation的时候,oracle才进行了谓词推入,而不是说view内部用到了Join字段的值.

下面这个例子里,网友认为 no_push_pred 没有起作用,其实这里起作用了,因为没有出现VIEW PUSHED PREDICATE.

而计划用来外部的值,进而走了索引,是因为闭包传递的原因.

SELECT /*+ no_push_pred(v) */

v.*
  FROM zt$test z,
       (SELECT owner,
               object_name,
               subobject_name,
               object_id,
               data_object_id,
               object_type,
               created,
               last_ddl_time,
               TIMESTAMP,
               status,
               temporary,
               generated,
               secondary
          FROM big_table1
        UNION ALL
        SELECT owner,
               object_name,
               subobject_name,
               object_id,
               data_object_id,
               object_type,
               created,
               last_ddl_time,
               TIMESTAMP,
               status,
               temporary,
               generated,
               secondary
          FROM big_table2) v
WHERE z.fid = v.object_id(+)
AND z.fid = 39;


PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------------
Plan hash value: 2324578912

---------------------------------------------------------------------------------------------
| Id  | Operation                      | Name       | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT               |            |     1 |   190   7  (15)| 00:00:01 |
|*  1 |  HASH JOIN OUTER               |            |     1 |   190   7  (15)| 00:00:01 |
|*  2 |   TABLE ACCESS FULL            | ZT$TEST    |     1 |    13   2   (0)| 00:00:01 |
|   3 |   VIEW                         |            |     2 |   354   4   (0)| 00:00:01 |
|   4 |    UNION-ALL                   |            |       |        |           |
|   5 |     TABLE ACCESS BY INDEX ROWID| BIG_TABLE1 |     1 |   177   2   (0)| 00:00:01 |
|*  6 |      INDEX RANGE SCAN          | IX_1       |     1 |         1   (0)| 00:00:01 |
|   7 |     TABLE ACCESS BY INDEX ROWID| BIG_TABLE2 |     1 |   177   2   (0)| 00:00:01 |
|*  8 |      INDEX RANGE SCAN          | IX_2       |     1 |         1   (0)| 00:00:01 |
---------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - access("Z"."FID"="V"."OBJECT_ID"(+))
   2 - filter("Z"."FID"=39)
   6 - access("OBJECT_ID"=39)
   8 - access("OBJECT_ID"=39)

 

闭包传递就是为了尽量多的更具等值推算出一些不用join就能拿到的明文信息,进而走出更多的索引路径.

WHERE z.fid = v.object_id(+)
AND z.fid = 39;

等价于

WHERE 39 = v.object_id(+)
AND z.fid = 39;

所以 select * from v where 39 = v.object_id(+) 走了索引是很正常的。

 

posted @ 2014-03-24 17:53  princessd8251  阅读(675)  评论(0)    收藏  举报