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