Oracle查询转换

概述

Oracle查询转换器的作用是把原始sql重写为语义相同的语句,目的是为了获得更高效的sql。
查询转换主要有四种技术:子查询展开,视图合并,谓词推入,星型转换。
了解查询转换是掌握SQL优化的基础,本文将对这四种技术做一些简单的介绍。

子查询展开

子查询展开是指优化器不再把子查询作为独立的单元处理,而是转换成等价的join方式。转换有两种方式:一是将子查询的结果集作为视图,与外层表或视图做join;二是将子查询中的表或视图拆出来,与外层表或视图做join。子查询前包含以下条件可以被展开:

  • any(= any和in等价)
  • all(<> all和not in等价)
  • exists
  • not exists
  • single row条件(where后面接=,<,>,<=,>=等条件)
    子查询展开的例子:
select e.empno,e.deptno from emp e where e.deptno in (select d.deptno from dept d where d.loc='CHICAGO');
---------------------------------------------------------------------------
| Id  | Operation       | Name | Rows  | Bytes | Cost (%CPU)| Time      |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |    5 |   235 |    6   (0)| 00:00:01 |
|*  1 |  HASH JOIN       |      |    5 |   235 |    6   (0)| 00:00:01 |
|*  2 |   TABLE ACCESS FULL| DEPT |    1 |    21 |    3   (0)| 00:00:01 |
|   3 |   TABLE ACCESS FULL| EMP  |    14 |   364 |    3   (0)| 00:00:01 |
---------------------------------------------------------------------------

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

   1 - access("E"."DEPTNO"="D"."DEPTNO")
   2 - filter("D"."LOC"='CHICAGO')

最终转换的语句:

SELECT "E"."EMPNO" "EMPNO","E"."DEPTNO" "DEPTNO" FROM "SCOTT"."DEPT" "D","SCOTT"."EMP" "E" WHERE "E"."DEPTNO"="D"."DEPTNO" AND "D"."LOC"='CHICAGO'

可以看到子查询中的dept表被拆出来,与外部查询块的emp表做inner join。可以这样转换的前提是dept表的deptno列是唯一键。如果deptno列不是唯一键,将做semi join(即所谓的半连接):

SQL> alter table dept drop constraint PK_DEPT CASCADE;

Table altered.

SQL> select e.empno,e.deptno from emp e where e.deptno in (select d.deptno from dept d where d.loc='CHICAGO');

6 rows selected.


Execution Plan
----------------------------------------------------------
Plan hash value: 230627304

---------------------------------------------------------------------------
| Id  | Operation       | Name | Rows  | Bytes | Cost (%CPU)| Time      |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |    5 |   235 |    6   (0)| 00:00:01 |
|*  1 |  HASH JOIN SEMI    |      |    5 |   235 |    6   (0)| 00:00:01 |
|   2 |   TABLE ACCESS FULL| EMP  |    14 |   364 |    3   (0)| 00:00:01 |
|*  3 |   TABLE ACCESS FULL| DEPT |    1 |    21 |    3   (0)| 00:00:01 |
---------------------------------------------------------------------------

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

   1 - access("E"."DEPTNO"="D"."DEPTNO")
   3 - filter("D"."LOC"='CHICAGO')

如果不做子查询展开,就会走filter类型的执行计划,并且子查询放在最后一步执行,作用是对emp全表扫描之后的结果集进行过滤:

select e.empno,e.deptno from emp e where e.deptno in (select  /*+ no_unnest*/ d.deptno from dept d where d.loc='CHICAGO');
----------------------------------------------------------------------------------------
| Id  | Operation             | Name    | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT         |           |    14 |   364 |     5     (0)| 00:00:01 |
|*  1 |  FILTER              |           |       |       |        |           |
|   2 |   TABLE ACCESS FULL         | EMP     |    14 |   364 |     3     (0)| 00:00:01 |
|*  3 |   TABLE ACCESS BY INDEX ROWID| DEPT    |     1 |    21 |     2     (0)| 00:00:01 |
|*  4 |    INDEX UNIQUE SCAN         | PK_DEPT |     1 |       |     1     (0)| 00:00:01 |
----------------------------------------------------------------------------------------

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

   1 - filter( EXISTS (SELECT /*+ NO_UNNEST */ 0 FROM "DEPT" "D" WHERE
          "D"."DEPTNO"=:B1 AND "D"."LOC"='CHICAGO'))
   3 - filter("D"."LOC"='CHICAGO')
   4 - access("D"."DEPTNO"=:B1)

看一个子查询结果集作为内联视图与外层查询块做join的例子:

select e.EMPLOYEE_ID from employees e where e.DEPARTMENT_ID in(select d.department_id from departments d,locations l where d.location_id=l.location_id and l.city='Venice');
-----------------------------------------------------------------------------------------------------
| Id  | Operation                       | Name              | Rows  | Bytes | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                |                   |    39 |   780 |     5   (0)| 00:00:01 |
|*  1 |  HASH JOIN SEMI                 |                   |    39 |   780 |     5   (0)| 00:00:01 |
|   2 |   VIEW                          | index$_join$_001  |   107 |   749 |     2   (0)| 00:00:01 |
|*  3 |    HASH JOIN                    |                   |       |       |            |          |
|   4 |     INDEX FAST FULL SCAN        | EMP_DEPARTMENT_IX |   107 |   749 |     1   (0)| 00:00:01 |
|   5 |     INDEX FAST FULL SCAN        | EMP_EMP_ID_PK     |   107 |   749 |     1   (0)| 00:00:01 |
|   6 |   VIEW                          | VW_NSO_1          |     4 |    52 |     3   (0)| 00:00:01 |
|   7 |    NESTED LOOPS                 |                   |     4 |    76 |     3   (0)| 00:00:01 |
|   8 |     NESTED LOOPS                |                   |     4 |    76 |     3   (0)| 00:00:01 |
|   9 |      TABLE ACCESS BY INDEX ROWID| LOCATIONS         |     1 |    12 |     2   (0)| 00:00:01 |
|* 10 |       INDEX RANGE SCAN          | LOC_CITY_IX       |     1 |       |     1   (0)| 00:00:01 |
|* 11 |      INDEX RANGE SCAN           | DEPT_LOCATION_IX  |     4 |       |     0   (0)| 00:00:01 |
|  12 |     TABLE ACCESS BY INDEX ROWID | DEPARTMENTS       |     4 |    28 |     1   (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------------

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

   1 - access("E"."DEPARTMENT_ID"="DEPARTMENT_ID")
   3 - access(ROWID=ROWID)
  10 - access("L"."CITY"='Venice')
  11 - access("D"."LOCATION_ID"="L"."LOCATION_ID")

执行计划仍然走了hash join semi,要使得转换是等价的,必须先完成子查询中departments和locations的join,结果集作为内联视图VM_NSO_1,与外层查询块的结果集做join。

下面的执行计划中,子查询的结果被作为nest loop的驱动表,为保证结果集正确,需要对子查询做hash unique去重。

select e.department_id from employees e where e.DEPARTMENT_ID in(select d.department_id from departments d,locations l where d.location_id=l.location_id and l.city='Venice');
------------------------------------------------------------------------------------------------------
| Id  | Operation                        | Name              | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                 |                   |    39 |   624 |     4  (25)| 00:00:01 |
|   1 |  NESTED LOOPS                    |                   |    39 |   624 |     4  (25)| 00:00:01 |
|   2 |   VIEW                           | VW_NSO_1          |     4 |    52 |     3   (0)| 00:00:01 |
|   3 |    HASH UNIQUE                   |                   |     4 |    76 |            |          |
|   4 |     NESTED LOOPS                 |                   |     4 |    76 |     3   (0)| 00:00:01 |
|   5 |      NESTED LOOPS                |                   |     4 |    76 |     3   (0)| 00:00:01 |
|   6 |       TABLE ACCESS BY INDEX ROWID| LOCATIONS         |     1 |    12 |     2   (0)| 00:00:01 |
|*  7 |        INDEX RANGE SCAN          | LOC_CITY_IX       |     1 |       |     1   (0)| 00:00:01 |
|*  8 |       INDEX RANGE SCAN           | DEPT_LOCATION_IX  |     4 |       |     0   (0)| 00:00:01 |
|   9 |      TABLE ACCESS BY INDEX ROWID | DEPARTMENTS       |     4 |    28 |     1   (0)| 00:00:01 |
|* 10 |   INDEX RANGE SCAN               | EMP_DEPARTMENT_IX |    10 |    30 |     0   (0)| 00:00:01 |
------------------------------------------------------------------------------------------------------

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

   7 - access("L"."CITY"='Venice')
   8 - access("D"."LOCATION_ID"="L"."LOCATION_ID")
  10 - access("E"."DEPARTMENT_ID"="DEPARTMENT_ID")

如果满足(不限于)下面的条件,子查询展开可能导致转换不等价,因此将不作展开:

  • where后面的连接符为=all或者<> any
  • exists后面的子查询中带有rownum
  • exists后面的子查询中带有having子句,cube子句或者rollup子句

例如下面的例子:

select e.department_id from employees e where e.department_id <>any (select department_id from departments d where d.location_id=1700);
-------------------------------------------------------------------------------------------------
| Id  | Operation                    | Name             | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |                  |    10 |    30 |    25   (0)| 00:00:01 |
|*  1 |  FILTER                      |                  |       |       |            |          |
|   2 |   TABLE ACCESS FULL          | EMPLOYEES        |   107 |   321 |     3   (0)| 00:00:01 |
|*  3 |   TABLE ACCESS BY INDEX ROWID| DEPARTMENTS      |     2 |    14 |     2   (0)| 00:00:01 |
|*  4 |    INDEX RANGE SCAN          | DEPT_LOCATION_IX |    21 |       |     1   (0)| 00:00:01 |
-------------------------------------------------------------------------------------------------

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

   1 - filter( EXISTS (SELECT 0 FROM "DEPARTMENTS" "D" WHERE "D"."LOCATION_ID"=1700 AND
              "DEPARTMENT_ID"<>:B1))
   3 - filter("DEPARTMENT_ID"<>:B1)
   4 - access("D"."LOCATION_ID"=1700)

视图合并

视图合并是指对包含视图的查询做出转换,使查询只包含基表。视图合并提供了更多的访问路径和join的可能性。也就是说,不做视图合并的执行计划包含在做了视图合并的执行计划中。下面的例子可以帮助理解这句话:

create view emp100 as select * from employees where MANAGER_ID=100;

select employee_id from emp100 where employee_id=120;
---------------------------------------------------------------------------------------------
| Id  | Operation            | Name        | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT        |            |      1 |      8 |      1   (0)| 00:00:01 |
|*  1 |  TABLE ACCESS BY INDEX ROWID| EMPLOYEES     |      1 |      8 |      1   (0)| 00:00:01 |
|*  2 |   INDEX UNIQUE SCAN        | EMP_EMP_ID_PK |      1 |        |      0   (0)| 00:00:01 |
---------------------------------------------------------------------------------------------

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

   1 - filter("MANAGER_ID"=100)
   2 - access("EMPLOYEE_ID"=120)

如果不做视图合并,执行计划如下:

select /*+ no_merge(a)*/ employee_id from emp100 a where employee_id=120;
----------------------------------------------------------------------------------------------
| Id  | Operation             | Name         | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT         |             |       1 |      13 |       1   (0)| 00:00:01 |
|   1 |  VIEW                 | EMP100         |       1 |      13 |       1   (0)| 00:00:01 |
|*  2 |   TABLE ACCESS BY INDEX ROWID| EMPLOYEES     |       1 |       8 |       1   (0)| 00:00:01 |
|*  3 |    INDEX UNIQUE SCAN         | EMP_EMP_ID_PK |       1 |         |       0   (0)| 00:00:01 |
----------------------------------------------------------------------------------------------

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

   2 - filter("MANAGER_ID"=100)
   3 - access("EMPLOYEE_ID"=120)

可以看到不对emp100做视图合并,执行计划中出现view字样,name列对应的就是视图名emp100。
第二个执行计划除了多了view的一行,访问路径和成本是和第一个相同的。
如果视图定义中包含下列内容,将不能做视图合并:

  • 集合操作符(UNION,UNION ALL,INTERSECT,MINUS)
  • connect by子句
  • rownum伪列
    做这些限制是为了防止视图合并之后得到错误的结果集。
    不能视图合并的例子:
create view emp_r1 as select * from employees where rownum<=10;

select employee_id from emp_r1 where employee_id=102;
-----------------------------------------------------------------------------------
| Id  | Operation      | Name      | Rows  | Bytes | Cost (%CPU)| Time      |
-----------------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |          |    10 |   130 |    1   (0)| 00:00:01 |
|*  1 |  VIEW          | EMP_R1      |    10 |   130 |    1   (0)| 00:00:01 |
|*  2 |   COUNT STOPKEY   |          |      |      |           |      |
|   3 |    INDEX FULL SCAN| EMP_EMP_ID_PK |    10 |    40 |    1   (0)| 00:00:01 |
-----------------------------------------------------------------------------------

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

   1 - filter("EMPLOYEE_ID"=102)
   2 - filter(ROWNUM<=10)

复杂视图合并

复杂视图合并技术允许对包含gourp by或者distinct的视图做展开。

create or replace view avg_salary_view as select department_id,avg(salary) avg_sal_dept from employees group by department_id;

select d.location_id,v.avg_sal_dept from departments d, avg_salary_view v where d.department_id=v.department_id and d.location_id=2400;
----------------------------------------------------------------------------------------------------
| Id  | Operation               | Name           | Rows  | Bytes | Cost (%CPU)| Time       |
----------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT           |           |     8 |   208 |     4  (25)| 00:00:01 |
|   1 |  HASH GROUP BY               |           |     8 |   208 |     4  (25)| 00:00:01 |
|   2 |   NESTED LOOPS               |           |    10 |   260 |     3   (0)| 00:00:01 |
|   3 |    NESTED LOOPS            |           |    10 |   260 |     3   (0)| 00:00:01 |
|   4 |     TABLE ACCESS BY INDEX ROWID| DEPARTMENTS       |     1 |    19 |     2   (0)| 00:00:01 |
|*  5 |      INDEX RANGE SCAN           | DEPT_LOCATION_IX  |     1 |       |     1   (0)| 00:00:01 |
|*  6 |     INDEX RANGE SCAN           | EMP_DEPARTMENT_IX |    10 |       |     0   (0)| 00:00:01 |
|   7 |    TABLE ACCESS BY INDEX ROWID | EMPLOYEES       |    10 |    70 |     1   (0)| 00:00:01 |
----------------------------------------------------------------------------------------------------

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

   5 - access("D"."LOCATION_ID"=2400)
   6 - access("D"."DEPARTMENT_ID"="DEPARTMENT_ID")

转换之后的sql文本为:
SELECT "D"."LOCATION_ID" "LOCATION_ID",AVG("EMPLOYEES"."SALARY") "AVG_SAL_DEPT" FROM "HR"."DEPARTMENTS" "D",HR."EMPLOYEES" "EMPLOYEES" WHERE "D"."DEPARTMENT_ID"="EMPLOYEES"."DEPARTMENT_ID" AND "D"."LOCATION_ID"=2400 GROUP BY "EMPLOYEES"."DEPARTMENT_ID","D".ROWID,"D"."LOCATION_ID"

_COMPLEX_VIEW_MERGING参数控制是否激活复杂视图合并,在9i之后默认为true,同时受OPTIMIZER_FEATURES_ENABLE参数控制:

SQL> @param
Enter value for nam: _complex_view_merging
old   8:    and a.ksppinm = '&nam'
new   8:    and a.ksppinm = '_complex_view_merging'

Parameter                       KSPPDESC                                                Session_Va Instance_V
-------------------------------------------------- ---------------------------------------------------------------------------------------------------- ---------- ----------
_complex_view_merging                   enable complex view merging                                        TRUE       TRUE

SQL> alter session set optimizer_features_enable='8.0.0';

Session altered.

SQL> @param
Enter value for nam: _complex_view_merging
old   8:    and a.ksppinm = '&nam'
new   8:    and a.ksppinm = '_complex_view_merging'

Parameter                       KSPPDESC                                                Session_Va Instance_V
-------------------------------------------------- ---------------------------------------------------------------------------------------------------- ---------- ----------
_complex_view_merging                   enable complex view merging                                        FALSE       TRUE

外连接视图合并

使用了外连接的sql中,视图合并需要满足下列条件之一:

  • 视图为外连接的驱动表
  • 视图的定义只包含单表
    下例中视图v1包含两张基表,在做外连接的驱动表时发生视图合并,做被驱动表则没有。
create view v1 as select a.EMPLOYEE_ID,a.JOB_ID,b.JOB_TITLE from employees a,jobs b where a.job_id=b.job_id;

select * from v1,departments d where v1.department_id(+)=d.department_id;
-----------------------------------------------------------------------------------------------
| Id  | Operation            | Name          | Rows  | Bytes | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT        |          |   106 |  7738 |     9  (23)| 00:00:01 |
|   1 |  MERGE JOIN OUTER        |          |   106 |  7738 |     9  (23)| 00:00:01 |
|   2 |   TABLE ACCESS BY INDEX ROWID    | DEPARTMENTS |    27 |   567 |     2    (0)| 00:00:01 |
|   3 |    INDEX FULL SCAN        | DEPT_ID_PK  |    27 |       |     1    (0)| 00:00:01 |
|*  4 |   SORT JOIN            |          |   107 |  5564 |     7  (29)| 00:00:01 |
|   5 |    VIEW             | V1          |   107 |  5564 |     6  (17)| 00:00:01 |
|   6 |     MERGE JOIN            |          |   107 |  4601 |     6  (17)| 00:00:01 |
|   7 |      TABLE ACCESS BY INDEX ROWID| JOBS          |    19 |   513 |     2    (0)| 00:00:01 |
|   8 |       INDEX FULL SCAN        | JOB_ID_PK   |    19 |       |     1    (0)| 00:00:01 |
|*  9 |      SORT JOIN            |          |   107 |  1712 |     4  (25)| 00:00:01 |
|  10 |       TABLE ACCESS FULL     | EMPLOYEES   |   107 |  1712 |     3    (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------

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

   4 - access("V1"."DEPARTMENT_ID"(+)="D"."DEPARTMENT_ID")
       filter("V1"."DEPARTMENT_ID"(+)="D"."DEPARTMENT_ID")
   9 - access("A"."JOB_ID"="B"."JOB_ID")
       filter("A"."JOB_ID"="B"."JOB_ID")

select * from v1,departments d where v1.department_id=d.department_id(+);
---------------------------------------------------------------------------------------------
| Id  | Operation              | Name        | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT          |         |    107 |  6848 |      9  (12)| 00:00:01 |
|*  1 |  HASH JOIN OUTER          |         |    107 |  6848 |      9  (12)| 00:00:01 |
|   2 |   MERGE JOIN              |         |    107 |  4601 |      6  (17)| 00:00:01 |
|   3 |    TABLE ACCESS BY INDEX ROWID| JOBS        |     19 |    513 |      2   (0)| 00:00:01 |
|   4 |     INDEX FULL SCAN          | JOB_ID_PK   |     19 |        |      1   (0)| 00:00:01 |
|*  5 |    SORT JOIN              |         |    107 |  1712 |      4  (25)| 00:00:01 |
|   6 |     TABLE ACCESS FULL          | EMPLOYEES   |    107 |  1712 |      3   (0)| 00:00:01 |
|   7 |   TABLE ACCESS FULL          | DEPARTMENTS |     27 |    567 |      3   (0)| 00:00:01 |
---------------------------------------------------------------------------------------------

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

   1 - access("A"."DEPARTMENT_ID"="D"."DEPARTMENT_ID"(+))
   5 - access("A"."JOB_ID"="B"."JOB_ID")
       filter("A"."JOB_ID"="B"."JOB_ID")

谓词推入

优化器在处理不能合并的视图时,可以选择将外部查询的谓词推入该视图的查询块,或者将视图中的谓词拉出到主查询。这样更早的处理视图的结果集,有可能会减小后续步骤操作所需的成本。
谓词推入到视图内部的例子:

create or replace view emp13 as select employee_id,department_id from employees where department_id=10 union  select employee_id,department_id from employees where department_id=110;

select employee_id from emp13 where employee_id<205;
----------------------------------------------------------------------------------------------------
| Id  | Operation                      | Name              | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT               |                   |     2 |    26 |     6  (34)| 00:00:01 |
|   1 |  VIEW                          | EMP13             |     2 |    26 |     6  (34)| 00:00:01 |
|   2 |   SORT UNIQUE                  |                   |     2 |    14 |     6  (34)| 00:00:01 |
|   3 |    UNION-ALL                   |                   |       |       |            |          |
|*  4 |     TABLE ACCESS BY INDEX ROWID| EMPLOYEES         |     1 |     7 |     2   (0)| 00:00:01 |
|*  5 |      INDEX RANGE SCAN          | EMP_DEPARTMENT_IX |     1 |       |     1   (0)| 00:00:01 |
|*  6 |     TABLE ACCESS BY INDEX ROWID| EMPLOYEES         |     1 |     7 |     2   (0)| 00:00:01 |
|*  7 |      INDEX RANGE SCAN          | EMP_DEPARTMENT_IX |     2 |       |     1   (0)| 00:00:01 |
----------------------------------------------------------------------------------------------------

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

   4 - filter("EMPLOYEE_ID"<205)
   5 - access("DEPARTMENT_ID"=10)
   6 - filter("EMPLOYEE_ID"<205)
   7 - access("DEPARTMENT_ID"=110)

注意到执行计划中条件EMPLOYEE_ID<205被推入到视图内部,将两张基表各过滤一次,然后对结果集做union。
两表关联时,连接条件也可以做推入,先来看不做谓词推入的执行计划:

select e.employee_id from emp13 e,departments d where e.department_id=d.department_id and d.manager_id=205;
------------------------------------------------------------------------------------------------------
| Id  | Operation                        | Name              | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                 |                   |     1 |    33 |     9  (23)| 00:00:01 |
|   1 |  NESTED LOOPS                    |                   |     1 |    33 |     9  (23)| 00:00:01 |
|   2 |   NESTED LOOPS                   |                   |     3 |    33 |     9  (23)| 00:00:01 |
|   3 |    VIEW                          | EMP13             |     3 |    78 |     6  (34)| 00:00:01 |
|   4 |     SORT UNIQUE                  |                   |     3 |    21 |     6  (34)| 00:00:01 |
|   5 |      UNION-ALL                   |                   |       |       |            |          |
|   6 |       TABLE ACCESS BY INDEX ROWID| EMPLOYEES         |     1 |     7 |     2   (0)| 00:00:01 |
|*  7 |        INDEX RANGE SCAN          | EMP_DEPARTMENT_IX |     1 |       |     1   (0)| 00:00:01 |
|   8 |       TABLE ACCESS BY INDEX ROWID| EMPLOYEES         |     2 |    14 |     2   (0)| 00:00:01 |
|*  9 |        INDEX RANGE SCAN          | EMP_DEPARTMENT_IX |     2 |       |     1   (0)| 00:00:01 |
|* 10 |    INDEX UNIQUE SCAN             | DEPT_ID_PK        |     1 |       |     0   (0)| 00:00:01 |
|* 11 |   TABLE ACCESS BY INDEX ROWID    | DEPARTMENTS       |     1 |     7 |     1   (0)| 00:00:01 |
------------------------------------------------------------------------------------------------------

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

   7 - access("DEPARTMENT_ID"=10)
   9 - access("DEPARTMENT_ID"=110)
  10 - access("E"."DEPARTMENT_ID"="D"."DEPARTMENT_ID")
  11 - filter("D"."MANAGER_ID"=205)

执行计划中emp13作为驱动表与departments表做nest loop,我们使用hint强制发生谓词推入:

select /*+ push_pred(e)*/ e.employee_id from emp13 e,departments d where e.department_id=d.department_id and d.manager_id=205;
------------------------------------------------------------------------------------------------------
| Id  | Operation                        | Name              | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                 |                   |     1 |    22 |     9  (23)| 00:00:01 |
|   1 |  NESTED LOOPS                    |                   |     1 |    22 |     9  (23)| 00:00:01 |
|*  2 |   TABLE ACCESS FULL              | DEPARTMENTS       |     1 |     7 |     3   (0)| 00:00:01 |
|   3 |   VIEW                           | EMP13             |     1 |    15 |     6  (34)| 00:00:01 |
|   4 |    SORT UNIQUE                   |                   |     3 |    21 |     6  (34)| 00:00:01 |
|   5 |     UNION ALL PUSHED PREDICATE   |                   |       |       |            |          |
|*  6 |      FILTER                      |                   |       |       |            |          |
|   7 |       TABLE ACCESS BY INDEX ROWID| EMPLOYEES         |     1 |     7 |     2   (0)| 00:00:01 |
|*  8 |        INDEX RANGE SCAN          | EMP_DEPARTMENT_IX |     1 |       |     1   (0)| 00:00:01 |
|*  9 |      FILTER                      |                   |       |       |            |          |
|  10 |       TABLE ACCESS BY INDEX ROWID| EMPLOYEES         |     2 |    14 |     2   (0)| 00:00:01 |
|* 11 |        INDEX RANGE SCAN          | EMP_DEPARTMENT_IX |     2 |       |     1   (0)| 00:00:01 |
------------------------------------------------------------------------------------------------------

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

   2 - filter("D"."MANAGER_ID"=205)
   6 - filter("D"."DEPARTMENT_ID"=10)
   8 - access("DEPARTMENT_ID"=10)
   9 - filter("D"."DEPARTMENT_ID"=110)
  11 - access("DEPARTMENT_ID"=110)

可以看到执行计划中出现PUSHED PREDICATE字样,条件e.department_id=d.department_id被转换成等值条件对employees表做过滤。join的谓词推入往往产生nest loop的执行计划(驱动表的每一行驱动被驱动表,来做谓词的过滤)。如果是大数据集的sql,可以使用hint no_push_pred或者设置参数_push_join_predicate为false禁止谓词推入。

星型转换

星型转换为提高星型查询的效率发生,在原有条件基础上会产生新的子查询对事实表做过滤,然后通过对事实表相应连接列的位图索引做位图操作,达到过滤事实表结果集的目的。
是否开启星型转换受参数star_transformation_enabled控制,可以设置为:
true:优化器将考虑基于成本的星型查询转换;
false:禁止星型转换;
temp_disable:优化器将考虑基于成本的星型查询转换,但是转换中不会使用临时表。
首先看一下星型转换的例子:

alter session set star_transformation_enabled=temp_disable;

select s.*,cu.cust_id from sales s,products p,customers cu, channels ch where s.prod_id=p.prod_id and s.cust_id=cu.cust_id and s.channel_id=ch.channel_id and cu.country_id =52789 and p.PROD_NAME='Bounce' and ch.CHANNEL_CLASS='Direct';
---------------------------------------------------------------------------------------------------------------------
| Id  | Operation                       | Name              | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |
---------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                |                   |  6379 |   604K|  2512   (1)| 00:00:31 |       |       |
|*  1 |  HASH JOIN                      |                   |  6379 |   604K|  2512   (1)| 00:00:31 |       |       |
|*  2 |   TABLE ACCESS FULL             | CUSTOMERS         |  7557 | 75570 |   405   (1)| 00:00:05 |       |       |
|   3 |   VIEW                          | VW_ST_9C0C7742    |  6380 |   542K|  2107   (1)| 00:00:26 |       |       |
|   4 |    NESTED LOOPS                 |                   |  6380 |   330K|  1696   (1)| 00:00:21 |       |       |
|   5 |     PARTITION RANGE ALL         |                   |  6380 |   149K|  1578   (1)| 00:00:19 |     1 |    28 |
|   6 |      BITMAP CONVERSION TO ROWIDS|                   |  6380 |   149K|  1578   (1)| 00:00:19 |       |       |
|   7 |       BITMAP AND                |                   |       |       |            |          |       |       |
|   8 |        BITMAP MERGE             |                   |       |       |            |          |       |       |
|   9 |         BITMAP KEY ITERATION    |                   |       |       |            |          |       |       |
|  10 |          BUFFER SORT            |                   |       |       |            |          |       |       |
|* 11 |           TABLE ACCESS FULL     | PRODUCTS          |     1 |    30 |     3   (0)| 00:00:01 |       |       |
|* 12 |          BITMAP INDEX RANGE SCAN| SALES_PROD_BIX    |       |       |            |          |     1 |    28 |
|  13 |        BITMAP MERGE             |                   |       |       |            |          |       |       |
|  14 |         BITMAP KEY ITERATION    |                   |       |       |            |          |       |       |
|  15 |          BUFFER SORT            |                   |       |       |            |          |       |       |
|* 16 |           TABLE ACCESS FULL     | CHANNELS          |     2 |    22 |     3   (0)| 00:00:01 |       |       |
|* 17 |          BITMAP INDEX RANGE SCAN| SALES_CHANNEL_BIX |       |       |            |          |     1 |    28 |
|  18 |        BITMAP MERGE             |                   |       |       |            |          |       |       |
|  19 |         BITMAP KEY ITERATION    |                   |       |       |            |          |       |       |
|  20 |          BUFFER SORT            |                   |       |       |            |          |       |       |
|* 21 |           TABLE ACCESS FULL     | CUSTOMERS         |  7557 | 75570 |   405   (1)| 00:00:05 |       |       |
|* 22 |          BITMAP INDEX RANGE SCAN| SALES_CUST_BIX    |       |       |            |          |     1 |    28 |
|  23 |     TABLE ACCESS BY USER ROWID  | SALES             |     1 |    29 |   530   (1)| 00:00:07 | ROWID | ROWID |
---------------------------------------------------------------------------------------------------------------------

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

   1 - access("ITEM_1"="CU"."CUST_ID")
   2 - filter("CU"."COUNTRY_ID"=52789)
  11 - filter("P"."PROD_NAME"='Bounce')
  12 - access("S"."PROD_ID"="P"."PROD_ID")
  16 - filter("CH"."CHANNEL_CLASS"='Direct')
  17 - access("S"."CHANNEL_ID"="CH"."CHANNEL_ID")
  21 - filter("CU"."COUNTRY_ID"=52789)
  22 - access("S"."CUST_ID"="CU"."CUST_ID")

注意到执行计划首先对各个维度表过滤出结果集,然后访问sales表连接列上的索引,做bitmap and操作之后,回表访问数据。事实上整个过程类似于将查询转换为如下等价sql:

select s.*,cu.cust_id from sales s,customers cu where s.cust_id=cu.cust_id and s.prod_id in (select p.prod_id from products p where p.PROD_NAME='Bounce') and s.channel_id in (select ch.channel_id from channels ch where ch.CHANNEL_CLASS='Direct') and cu.country_id =52789;

下面看一下star_transformation_enabled参数设置为true的结果:

alter session set star_transformation_enabled=true;

select s.*,cu.cust_id from sales s,products p,customers cu, channels ch where s.prod_id=p.prod_id and s.cust_id=cu.cust_id and s.channel_id=ch.channel_id and cu.country_id =52789 and p.PROD_NAME='Bounce' and ch.CHANNEL_CLASS='Direct';
------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                        | Name                      | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |
------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                 |                           |  6380 |   573K|  1717   (1)| 00:00:21 |       |       |
|   1 |  TEMP TABLE TRANSFORMATION       |                           |       |       |            |          |       |       |
|   2 |   LOAD AS SELECT                 | SYS_TEMP_0FD9D6601_11F1D1 |       |       |            |          |       |       |
|*  3 |    TABLE ACCESS FULL             | CUSTOMERS                 |  7557 | 75570 |   405   (1)| 00:00:05 |       |       |
|*  4 |   HASH JOIN                      |                           |  6380 |   573K|  1312   (0)| 00:00:16 |       |       |
|   5 |    TABLE ACCESS FULL             | SYS_TEMP_0FD9D6601_11F1D1 |  7557 | 37785 |     5   (0)| 00:00:01 |       |       |
|   6 |    VIEW                          | VW_ST_51173BA3            |  6380 |   542K|  1307   (0)| 00:00:16 |       |       |
|   7 |     NESTED LOOPS                 |                           |  6380 |   330K|  1296   (0)| 00:00:16 |       |       |
|   8 |      PARTITION RANGE ALL         |                           |  6380 |   149K|  1178   (1)| 00:00:15 |     1 |    28 |
|   9 |       BITMAP CONVERSION TO ROWIDS|                           |  6380 |   149K|  1178   (1)| 00:00:15 |       |       |
|  10 |        BITMAP AND                |                           |       |       |            |          |       |       |
|  11 |         BITMAP MERGE             |                           |       |       |            |          |       |       |
|  12 |          BITMAP KEY ITERATION    |                           |       |       |            |          |       |       |
|  13 |           BUFFER SORT            |                           |       |       |            |          |       |       |
|* 14 |            TABLE ACCESS FULL     | PRODUCTS                  |     1 |    30 |     3   (0)| 00:00:01 |       |       |
|* 15 |           BITMAP INDEX RANGE SCAN| SALES_PROD_BIX            |       |       |            |          |     1 |    28 |
|  16 |         BITMAP MERGE             |                           |       |       |            |          |       |       |
|  17 |          BITMAP KEY ITERATION    |                           |       |       |            |          |       |       |
|  18 |           BUFFER SORT            |                           |       |       |            |          |       |       |
|* 19 |            TABLE ACCESS FULL     | CHANNELS                  |     2 |    22 |     3   (0)| 00:00:01 |       |       |
|* 20 |           BITMAP INDEX RANGE SCAN| SALES_CHANNEL_BIX         |       |       |            |          |     1 |    28 |
|  21 |         BITMAP MERGE             |                           |       |       |            |          |       |       |
|  22 |          BITMAP KEY ITERATION    |                           |       |       |            |          |       |       |
|  23 |           BUFFER SORT            |                           |       |       |            |          |       |       |
|  24 |            TABLE ACCESS FULL     | SYS_TEMP_0FD9D6601_11F1D1 |  7557 | 37785 |     5   (0)| 00:00:01 |       |       |
|* 25 |           BITMAP INDEX RANGE SCAN| SALES_CUST_BIX            |       |       |            |          |     1 |    28 |
|  26 |      TABLE ACCESS BY USER ROWID  | SALES                     |     1 |    29 |   130   (0)| 00:00:02 | ROWID | ROWID |
------------------------------------------------------------------------------------------------------------------------------

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

   3 - filter("CU"."COUNTRY_ID"=52789)
   4 - access("ITEM_1"="C0")
  14 - filter("P"."PROD_NAME"='Bounce')
  15 - access("S"."PROD_ID"="P"."PROD_ID")
  19 - filter("CH"."CHANNEL_CLASS"='Direct')
  20 - access("S"."CHANNEL_ID"="CH"."CHANNEL_ID")
  25 - access("S"."CUST_ID"="C0")

注意到执行计划中先对customers根据过滤条件cu.country_id =52789得到临时表SYS_TEMP_0FD9D6601_11F1D1,后续步骤中每次需要访问customers表时则由临时表来替换,这也是为了总共减少所访问的数据量所考虑。
星型转换同样有一些限制条件,本文暂不讨论。

以上是对四类查询转换概念性的描述,对于具体的应用场景中的SQL要具体分析如何利用这些技术。查询转换还有诸如子查询合并,连接因式分解,表扩展,表裁剪,物化视图重写等技术。有机会将再写文章介绍,或者有兴趣的同学自行研究。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值