SQL优化【基础09】 - 查询转换(query transformation)

--version:11.2
前言:为什么要查询转换呢?
简单的说就是ORACLE在等逻辑的基础上增加可能存在的执行计划,有更多的选择,从中选出最优的计划;

常见的3种转换
1.视图合并
2.子查询展开
3.谓词推入


create view v_t1t2 as 
select  t1.object_id,t2.status
from t1,t2
where t1.object_id=t2.object_id;

SQL> create view v_t1t2 as 
  2  select  t1.object_id,t2.status
  3  from t1,t2
  4  where t1.object_id=t2.object_id;

View created.

SQL> create table t1 as select * from dba_objects;

Table created.

SQL> create table t2 as select * from dba_objects where rownum<1001;

Table created.

SQL> create table t3 as select * from dba_objects where rownum<11;

Table created.

一. 视图合并

--如下执行SQL,视图v_t1t2没有出现在name列
select  t3.object_id,vv.status
from t3,v_t1t2  vv
where t3.object_id=vv.object_id;

SQL_ID  9w0yg7pp6kph3, child number 0
-------------------------------------
 select  t3.object_id,vv.status from t3,v_t1t2  vv where
t3.object_id=vv.object_id

Plan hash value: 2467348796

-----------------------------------------------------------------------------------------------------------------
| Id  | Operation           | Name | Starts | E-Rows | A-Rows |   A-Time   | Buffers |  OMem |  1Mem | Used-Mem |
-----------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT    |      |      1 |        |      9 |00:00:02.03 |    1060 |       |       |          |
|*  1 |  HASH JOIN          |      |      1 |      1 |      9 |00:00:02.03 |    1060 |  1306K|  1306K| 1128K (0)|
|*  2 |   HASH JOIN         |      |      1 |     10 |     10 |00:00:02.01 |    1043 |  1517K|  1517K| 1123K (0)|
|   3 |    TABLE ACCESS FULL| T3   |      1 |     10 |     10 |00:00:00.01 |       3 |       |       |          |
|   4 |    TABLE ACCESS FULL| T1   |      1 |  62548 |  72668 |00:00:00.55 |    1040 |       |       |          |
|   5 |   TABLE ACCESS FULL | T2   |      1 |   1000 |   1000 |00:00:00.01 |      17 |       |       |          |
-----------------------------------------------------------------------------------------------------------------

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

   1 - access("T1"."OBJECT_ID"="T2"."OBJECT_ID")
   2 - access("T3"."OBJECT_ID"="T1"."OBJECT_ID")

Note
-----
   - dynamic sampling used for this statement (level=2)
   
--加入no_merge的hint可以name列看到v_t1t2的视图名称   
 select  /*+ no_merge(vv) */t3.object_id,vv.status from t3,v_t1t2  vv where
t3.object_id=vv.object_id;   

SQL_ID  4bkn59yaq6chu, child number 0
-------------------------------------
      select  /*+ no_merge(vv) */t3.object_id,vv.status from t3,v_t1t2
vv where t3.object_id=vv.object_id

Plan hash value: 1147613535

-------------------------------------------------------------------------------
| Id  | Operation            | Name   | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------
|   0 | SELECT STATEMENT     |        |       |       |   300 (100)|          |
|*  1 |  HASH JOIN           |        |    10 |   310 |   300   (1)| 00:00:04 |
|   2 |   TABLE ACCESS FULL  | T3     |    10 |   130 |     3   (0)| 00:00:01 |
|   3 |   VIEW               | V_T1T2 |   999 | 17982 |   297   (1)| 00:00:04 |
|*  4 |    HASH JOIN         |        |   999 | 30969 |   297   (1)| 00:00:04 |
|   5 |     TABLE ACCESS FULL| T2     |  1000 | 18000 |     6   (0)| 00:00:01 |
|   6 |     TABLE ACCESS FULL| T1     | 62548 |   794K|   290   (1)| 00:00:04 |
-------------------------------------------------------------------------------


二.子查询展开

--如下SQL的子查询中的T3与外边的T1形成HASH JOIN RIGHT SEMI(HASH半连接)
select  t1.status,t2.object_name
from t1,t2
where t1.object_id=t2.object_id
and t1.object_id in (select object_id from t3 );

select  t1.status,t2.object_name from t1,t2 where
t1.object_id=t2.object_id and t1.object_id in (select object_id from t3
)

Plan hash value: 2897334926

------------------------------------------------------------------------------
| Id  | Operation             | Name | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------
|   0 | SELECT STATEMENT      |      |       |       |   301 (100)|          |
|*  1 |  HASH JOIN            |      |     1 |   110 |   301   (1)| 00:00:04 |
|*  2 |   HASH JOIN RIGHT SEMI|      |    10 |   310 |   294   (1)| 00:00:04 |
|   3 |    TABLE ACCESS FULL  | T3   |    10 |   130 |     3   (0)| 00:00:01 |
|   4 |    TABLE ACCESS FULL  | T1   | 62548 |  1099K|   290   (1)| 00:00:04 |
|   5 |   TABLE ACCESS FULL   | T2   |  1000 | 79000 |     6   (0)| 00:00:01 |
------------------------------------------------------------------------------


--加入no_unnest的hint可以看到不展开将会以filter的连接方式来执行子查询SQL
select  t1.status,t2.object_name
from t1,t2
where t1.object_id=t2.object_id
and t1.object_id in (select /*+ no_unnest */object_id from t3 );

select  t1.status,t2.object_name from t1,t2 where
t1.object_id=t2.object_id and t1.object_id in (select /*+ no_unnest
*/object_id from t3 )

Plan hash value: 2685818224

----------------------------------------------------------------------------
| Id  | Operation           | Name | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------
|   0 | SELECT STATEMENT    |      |       |       |  1796 (100)|          |
|*  1 |  FILTER             |      |       |       |            |          |
|*  2 |   HASH JOIN         |      |   999 | 96903 |   297   (1)| 00:00:04 |
|   3 |    TABLE ACCESS FULL| T2   |  1000 | 79000 |     6   (0)| 00:00:01 |
|   4 |    TABLE ACCESS FULL| T1   | 62548 |  1099K|   290   (1)| 00:00:04 |
|*  5 |   TABLE ACCESS FULL | T3   |     1 |    13 |     3   (0)| 00:00:01 |
----------------------------------------------------------------------------

三.谓词推入


SQL> create index idx_t1_id on t1(object_id);

Index created.

SQL> create index idx_t2_id on t2(object_id);

Index created.

SQL> create index idx_t3_id on t3(object_id);

Index created.


select /*+  push_pred(vv) no_merge(vv)  */ t3.status,vv.object_id from
t3,v_t1t2 vv where t3.object_id=vv.object_id(+) and t3.object_id=99

Plan hash value: 3425305862

------------------------------------------------------------------------------------------
| Id  | Operation                    | Name      | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |           |       |       |     3 (100)|          |
|   1 |  NESTED LOOPS OUTER          |           |     1 |    31 |     3   (0)| 00:00:01 |
|   2 |   TABLE ACCESS BY INDEX ROWID| T3        |     1 |    18 |     1   (0)| 00:00:01 |
|*  3 |    INDEX RANGE SCAN          | IDX_T3_ID |     1 |       |     1   (0)| 00:00:01 |
|   4 |   VIEW PUSHED PREDICATE      | V_T1T2    |     1 |    13 |     2   (0)| 00:00:01 |     --谓词推入关键字
|*  5 |    FILTER                    |           |       |       |            |          |
|   6 |     MERGE JOIN CARTESIAN     |           |     1 |    26 |     2   (0)| 00:00:01 |
|*  7 |      INDEX RANGE SCAN        | IDX_T1_ID |     1 |    13 |     1   (0)| 00:00:01 |
|   8 |      BUFFER SORT             |           |     1 |    13 |     1   (0)| 00:00:01 |
|*  9 |       INDEX RANGE SCAN       | IDX_T2_ID |     1 |    13 |     1   (0)| 00:00:01 |
------------------------------------------------------------------------------------------



四.查询转换与应用
--那么查询转换与优化有什么关系?为什么要去了解?
看个例子,我们将上面的其中一条关于谓词推入的SQL改写下:
--不让它进行谓词推入
select /*+  no_push_pred(vv) merge(vv)  */ t3.status,vv.object_id from
t3,v_t1t2 vv where t3.object_id=vv.object_id(+) and t3.object_id=99

select /*+  no_push_pred(vv) merge(vv)  */ t3.status,vv.object_id from
t3,v_t1t2 vv where t3.object_id=vv.object_id(+) and t3.object_id=99

Plan hash value: 2569520452

------------------------------------------------------------------------------------------
| Id  | Operation                    | Name      | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |           |       |       |     4 (100)|          |
|*  1 |  HASH JOIN OUTER             |           |     1 |    31 |     4  (25)| 00:00:01 |
|   2 |   TABLE ACCESS BY INDEX ROWID| T3        |     1 |    18 |     1   (0)| 00:00:01 |
|*  3 |    INDEX RANGE SCAN          | IDX_T3_ID |     1 |       |     1   (0)| 00:00:01 |
|   4 |   VIEW                       | V_T1T2    |     1 |    13 |     2   (0)| 00:00:01 |
|   5 |    MERGE JOIN CARTESIAN      |           |     1 |    26 |     2   (0)| 00:00:01 |
|*  6 |     INDEX RANGE SCAN         | IDX_T1_ID |     1 |    13 |     1   (0)| 00:00:01 |
|   7 |     BUFFER SORT              |           |     1 |    13 |     1   (0)| 00:00:01 |
|*  8 |      INDEX RANGE SCAN        | IDX_T2_ID |     1 |    13 |     1   (0)| 00:00:01 |
------------------------------------------------------------------------------------------

--然后去除HINT默认让它运行
select  t3.status,vv.object_id from
t3,v_t1t2 vv where t3.object_id=vv.object_id(+) and t3.object_id=99;

SQL_ID  f3x5z80ammrb2, child number 0
-------------------------------------
select  t3.status,vv.object_id from t3,v_t1t2 vv where
t3.object_id=vv.object_id(+) and t3.object_id=99

Plan hash value: 3425305862

------------------------------------------------------------------------------------------
| Id  | Operation                    | Name      | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |           |       |       |     3 (100)|          |
|   1 |  NESTED LOOPS OUTER          |           |     1 |    31 |     3   (0)| 00:00:01 |
|   2 |   TABLE ACCESS BY INDEX ROWID| T3        |     1 |    18 |     1   (0)| 00:00:01 |
|*  3 |    INDEX RANGE SCAN          | IDX_T3_ID |     1 |       |     1   (0)| 00:00:01 |
|   4 |   VIEW PUSHED PREDICATE      | V_T1T2    |     1 |    13 |     2   (0)| 00:00:01 |
|*  5 |    FILTER                    |           |       |       |            |          |
|   6 |     MERGE JOIN CARTESIAN     |           |     1 |    26 |     2   (0)| 00:00:01 |
|*  7 |      INDEX RANGE SCAN        | IDX_T1_ID |     1 |    13 |     1   (0)| 00:00:01 |
|   8 |      BUFFER SORT             |           |     1 |    13 |     1   (0)| 00:00:01 |
|*  9 |       INDEX RANGE SCAN       | IDX_T2_ID |     1 |    13 |     1   (0)| 00:00:01 |
------------------------------------------------------------------------------------------

--可以看到推入后的COST值,ORACLE计算的结果为3比上一条推入的4略小;
在日常的运维中如果发现SQL在推入后的效果更佳时你就可以考虑推入这个方法去暂时解决这个问题,以及要清楚
哪些条件可让ORACLE进入推入的操作,哪些不能; 
如下举例,不同类型的视图,相同的连接类型,一个可以推入,一个不行;
        create view v_tt as 
  2    select t2.object_id,t1.status 
  3    from t1,t2
  4    where t1.object_id=t2.object_id
  5    union 
  6    select t1.object_id,t3.status
  7    from t1,t3
  8    where t1.object_id=t3.object_id
  9    ;

SQL_ID  dttpqvxhpjwm4, child number 0
-------------------------------------
   select  /*+ push_pred(v_tt) */t1.status,v_tt.object_id    from v_tt
,t1     where v_tt.object_id=t1.object_id(+)     and t1.object_id=88

Plan hash value: 1281273642

------------------------------------------------------------------------------------------------
| Id  | Operation                          | Name      | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                   |           |       |       |     9 (100)|          |
|   1 |  NESTED LOOPS                      |           |     2 |    40 |     9  (23)| 00:00:01 |
|   2 |   TABLE ACCESS BY INDEX ROWID      | T1        |     1 |    18 |     2   (0)| 00:00:01 |
|*  3 |    INDEX RANGE SCAN                | IDX_T1_ID |     1 |       |     1   (0)| 00:00:01 |
|   4 |   VIEW                             | V_TT      |     2 |     4 |     7  (29)| 00:00:01 |
|   5 |    SORT UNIQUE                     |           |     2 |    62 |     7  (58)| 00:00:01 |
|   6 |     UNION ALL PUSHED PREDICATE     |           |       |       |            |          |     --推入
|*  7 |      FILTER                        |           |       |       |            |          |
|   8 |       MERGE JOIN CARTESIAN         |           |     1 |    31 |     3   (0)| 00:00:01 |
|   9 |        TABLE ACCESS BY INDEX ROWID | T1        |     1 |    18 |     2   (0)| 00:00:01 |
|* 10 |         INDEX RANGE SCAN           | IDX_T1_ID |     1 |       |     1   (0)| 00:00:01 |
|  11 |        BUFFER SORT                 |           |     1 |    13 |     1   (0)| 00:00:01 |
|* 12 |         INDEX RANGE SCAN           | IDX_T2_ID |     1 |    13 |     1   (0)| 00:00:01 |
|* 13 |      FILTER                        |           |       |       |            |          |
|  14 |       MERGE JOIN CARTESIAN         |           |     1 |    31 |     2   (0)| 00:00:01 |
|* 15 |        INDEX RANGE SCAN            | IDX_T1_ID |     1 |    13 |     1   (0)| 00:00:01 |
|  16 |        BUFFER SORT                 |           |     1 |    18 |     1   (0)| 00:00:01 |
|  17 |         TABLE ACCESS BY INDEX ROWID| T3        |     1 |    18 |     1   (0)| 00:00:01 |
|* 18 |          INDEX RANGE SCAN          | IDX_T3_ID |     1 |       |     0   (0)|          |
------------------------------------------------------------------------------------------------


--把v_t1t2代替原来v_tt视图,设置了no_merge,push_pred依然不能推入,视图类型不一样;
SQL_ID  7wq68281uxuah, child number 0
-------------------------------------
       select  /*+ no_merge(v_tt)  push_pred(v_tt)
*/t1.status,v_tt.object_id    from v_t1t2 v_tt ,t1     where
v_tt.object_id=t1.object_id(+)     and t1.object_id=88

Plan hash value: 1712600844

-------------------------------------------------------------------------------------------
| Id  | Operation                     | Name      | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT              |           |       |       |     4 (100)|          |
|   1 |  MERGE JOIN CARTESIAN         |           |     1 |    31 |     4   (0)| 00:00:01 |
|   2 |   VIEW                        | V_T1T2    |     1 |    13 |     2   (0)| 00:00:01 |
|   3 |    MERGE JOIN CARTESIAN       |           |     1 |    26 |     2   (0)| 00:00:01 |
|*  4 |     INDEX RANGE SCAN          | IDX_T1_ID |     1 |    13 |     1   (0)| 00:00:01 |
|   5 |     BUFFER SORT               |           |     1 |    13 |     1   (0)| 00:00:01 |
|*  6 |      INDEX RANGE SCAN         | IDX_T2_ID |     1 |    13 |     1   (0)| 00:00:01 |
|   7 |   BUFFER SORT                 |           |     1 |    18 |     4   (0)| 00:00:01 |
|   8 |    TABLE ACCESS BY INDEX ROWID| T1        |     1 |    18 |     2   (0)| 00:00:01 |
|*  9 |     INDEX RANGE SCAN          | IDX_T1_ID |     1 |       |     1   (0)| 00:00:01 |
-------------------------------------------------------------------------------------------


其它的查询转换类型还包含有:
连接因式分解,表扩展(table expansion),表移除,in的改写形式 


--连接因式分解(VW_JF_XX),JF:join factorization,关键提示字眼
概念:针对union all语句,对其中的公共部份拿出再与原SQL的剩余部份做连接
条件:须等价语义转换这个大前提


--表扩展(table expansion)
概念:其中一个分区的索引失效并不影响其它分区的索引使用情况,11G R2前一个分区索引失效,走索引会全部转为全表扫描;
前提:基于成本考虑
expand_table(t1)/no_expand_table(t1)


--表移除
概念:把不影响SQL执行结果的表删除,少做一次连接

备注:lnnvl函数lnnvl(deptno in (10) )

--IN写法
(1)第一种
in-list iterator
可被10142,10157事件禁止

(2)第二种
in-list iterator expansion
概念:将in后面的变量用union all来连接各个分支,保证每个分去的计划正确性,缺点分支越长越耗解析时长
,并且当in-list iterator生效时,使用use_concat(此HINT)并不生效,可用10142,10157将interator禁掉

(3)第三种
in做为filter的过滤条件
满足两条件a.in后面为子查询而非常量 b. in后面的子查询不可展开

(4)第四种
子查询展开与(子查询中包含视图)的合并


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章