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要具體分析如何利用這些技術。查詢轉換還有諸如子查詢合併,連接因式分解,表擴展,表裁剪,物化視圖重寫等技術。有機會將再寫文章介紹,或者有興趣的同學自行研究。

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