CBQT(Cost Based Query Transformation)
Query Transformer 가 쿼리블럭을 최적화 하는 방법을 정할 때 자신이 결정하지 못하고 Cost Estimator 에게 어떤 방법이 제일 좋은 것인지 물어보고 결정하는 것
예제
SELECT COUNT(*)
FROM tab1
WHERE tab1.col2 IN (SELECT tab2.col2
FROM tab2, tab3
WHERE tab2.col1 = tab3.col1
AND tab2.col3 = 1
AND tab3.col4 = 2
)
AND tab1.col5 = 4;
Search Type
변환 가능한 경우의 수를 어디까지 고려할 것인지의 정도(Level)를 설명한 것
케이스 | 경우의수 | 설명 | 예제표시법 |
CASE1 | SUBQ1 Unnest, SUBQ2 Unnest | 모두 Unnesting 됨 | (1,1) |
CASE2 | SUBQ1 Unnest, SUBQ2 No Unnest | SUBQ1만 Unnesting 됨 | (1,0) |
CASE3 | SUBQ1 No Unnest, SUBQ2 Unnest | SUBQ2만 Unnesting 됨 | (0,1) |
CASE4 | SUBQ1 No Unnest, SUBQ2 No Unnest | 모두 변화 없음 | (0,0) |
특성 | Search Type | 설명 |
가장 강력한 | Exhaustive | 위의 예에서 4가지 경우를 모두 고려 하여 Cost 가 가장 저렴한 SQL을 선택 |
순차적 비교 | Linear | 기본값, 위의 예에서 순차적으로 Unnesting 고려(SUBQ1 확정 후 SUBQ2 확정) |
※ 나머지 Iterative, Two_Pass, On, Off 는 4장 에서 설명
Iteration
변환이 수행된 경우와 수행되지 않은 경우의 Cost를 비교하기 위한 경우의 수
케이스 | 예제표시법 | Iteration |
CASE1 | (1,1) | Starting Iteration 1 |
CASE2 | (1,0) | Starting Iteration 2 |
CASE3 | (0,1) | Starting Iteration 3 |
CASE4 | (0,0) | Starting Iteration 4 |
예제1(CBPPD)
create index emp_sal_ix on emp(sal);
select /*+ qb_name(v_main) */
deptno, max_sal
from (select /*+ qb_name(iv) no_merge */
deptno, max(sal) max_sal
from emp e
group by deptno)
where max_sal > 3290;
-------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | A-Rows | A-Time | Buffers | OMem | 1Mem | Used-Mem |
-------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 1 |00:00:00.01 | 2 | | | |
| 1 | VIEW | | 1 | 1 |00:00:00.01 | 2 | | | |
|* 2 | FILTER | | 1 | 1 |00:00:00.01 | 2 | | | |
| 3 | HASH GROUP BY | | 1 | 1 |00:00:00.01 | 2 | 941K| 941K| 750K (0)|
| 4 | TABLE ACCESS BY INDEX ROWID| EMP | 1 | 1 |00:00:00.01 | 2 | | | |
|* 5 | INDEX RANGE SCAN | EMP_SAL_IX | 1 | 1 |00:00:00.01 | 1 | | | |
-------------------------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - filter(MAX("SAL")>3290)
5 - access("SAL">3290)
-- max_sal > 3290 조건 으로 e.sal > 3290 조건을 추가 함에 따라 emp_sal_ix 활용 됨 (아래 SQL 참조)
select /*+ qb_name(v_main) */
deptno, max_sal
from (select /*+ qb_name(iv) no_merge */
deptno, max(sal) max_sal
from emp e
where e.sal > 3290
group by deptno
having max(sal) > 3290);
-------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | A-Rows | A-Time | Buffers | OMem | 1Mem | Used-Mem |
-------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 1 |00:00:00.01 | 2 | | | |
| 1 | VIEW | | 1 | 1 |00:00:00.01 | 2 | | | |
|* 2 | FILTER | | 1 | 1 |00:00:00.01 | 2 | | | |
| 3 | HASH GROUP BY | | 1 | 1 |00:00:00.01 | 2 | 941K| 941K| 480K (0)|
| 4 | TABLE ACCESS BY INDEX ROWID| EMP | 1 | 1 |00:00:00.01 | 2 | | | |
|* 5 | INDEX RANGE SCAN | EMP_SAL_IX | 1 | 1 |00:00:00.01 | 1 | | | |
-------------------------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - filter(MAX("SAL")>3290)
5 - access("E"."SAL">3290)
-- 10053 Trace
********************************
COST-BASED QUERY TRANSFORMATIONS
********************************
FPD: Considering simple filter push (pre rewrite) in query block V_MAIN (#0)
FPD: Current where clause predicates "from$_subquery$_001"."MAX_SAL">3290
try to generate transitive predicate from check constraints for query block V_MAIN (#0)
finally: "from$_subquery$_001"."MAX_SAL">3290
FPD: Following are pushed to having clause of query block IV (#0) <<<<<<<<<<<<<<<<<<<<<< FPD
MAX("E"."SAL")>3290 <<<<<<<<<<<<<<<<<<<<<<
FPD: Considering simple filter push (pre rewrite) in query block IV (#0)
FPD: Current where clause predicates ??
************************************
Cost-based predicate pushdown (JPPD)
************************************
...
JPPD: Applying transformation directives
JPPD: Checking validity of push-down in query block V_MAIN (#1)
JPPD: No valid views found to push predicate into.
query block V_MAIN (#1) unchanged
FPD: Considering simple filter push in query block V_MAIN (#1)
??
FPD: Considering simple filter push in query block IV (#2) <<<<<<<<<<<<<<<<<<<<<< CBPPD
"E"."SAL">3290 <<<<<<<<<<<<<<<<<<<<<<
try to generate transitive predicate from check constraints for query block IV (#2) <<<<<<<<<<<<<<<<<<<<<<
finally: "E"."SAL">3290 <<<<<<<<<<<<<<<<<<<<<<
OJE: Begin: find best directive for query block IV (#2)
OJE: End: finding best directive for query block IV (#2)
Final query after transformations:******* UNPARSED QUERY IS *******
SELECT /*+ QB_NAME ("V_MAIN") */ "from$_subquery$_001"."DEPTNO" "DEPTNO","from$_subquery$_001"."MAX_SAL" "MAX_SAL" FROM (SELECT /*+ NO_MERGE QB_NAME ("IV")
*/ "E"."DEPTNO" "DEPTNO",MAX("E"."SAL") "MAX_SAL" FROM "SCOTT"."EMP" "E" WHERE "E"."SAL">3290 GROUP BY "E"."DEPTNO" HAVING MAX("E"."SAL")>3290) "from$_subque
ry$_001"
kkoqbc: optimizing query block IV (#2)
...
예제2(CBPPD+CVM)
-- CBPPD 에 no_merge 힌트가 반드시 필요한 것은 아니다
select /*+ qb_name(v_main) */
d.deptno, d.dname, max_sal
from dept d,
(select /*+ qb_name(v_sub) */
e.deptno, max(e.sal) max_sal
from emp e
group by e.deptno) e1
where d.deptno = e1.deptno
and e1.max_sal > 1520;
------------------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | A-Rows | A-Time | Buffers | Reads | OMem | 1Mem| Used-Mem |
------------------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 3 |00:00:00.01 | 7 | 2 | | | |
| 1 | NESTED LOOPS | | 1 | 3 |00:00:00.01 | 7 | 2 | | | |
| 2 | NESTED LOOPS | | 1 | 3 |00:00:00.01 | 4 | 1 | | | |
| 3 | VIEW | | 1 | 3 |00:00:00.01 | 2 | 0 | | | |
|* 4 | FILTER | | 1 | 3 |00:00:00.01 | 2 | 0 | | | |
| 5 | HASH GROUP BY | | 1 | 3 |00:00:00.01 | 2 | 0 | 941K| 941K| 1234K (0)|
| 6 | TABLE ACCESS BY INDEX ROWID| EMP | 1 | 7 |00:00:00.01 | 2 | 0 | | | |
|* 7 | INDEX RANGE SCAN | EMP_SAL_IX | 1 | 7 |00:00:00.01 | 1 | 0 | | | |
|* 8 | INDEX UNIQUE SCAN | PK_DEPT | 3 | 3 |00:00:00.01 | 2 | 1 | | | |
| 9 | TABLE ACCESS BY INDEX ROWID | DEPT | 3 | 3 |00:00:00.01 | 3 | 1 | | | |
------------------------------------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
4 - filter(MAX("E"."SAL")>1520)
7 - access("E"."SAL">1520)
8 - access("D"."DEPTNO"="E1"."DEPTNO")
-- 10053 Trace
**************************
Predicate Move-Around (PM)
**************************
PM: Passed validity checks.
PM: Pulled up predicate "E1"."MAX_SAL">1520 <<<<<<<<<<<<<<<<<<<<<< PM 수행 후 CBPPD 수행 (PM FALSE => CBPPD FALSE)
from query block V_SUB (#2) to query block V_MAIN (#1)
PM: PM bypassed: checking.
********************************
COST-BASED QUERY TRANSFORMATIONS
********************************
FPD: Considering simple filter push (pre rewrite) in query block V_MAIN (#0)
FPD: Current where clause predicates "D"."DEPTNO"="E1"."DEPTNO" AND "E1"."MAX_SAL">1520
try to generate transitive predicate from check constraints for query block V_MAIN (#0)
finally: "D"."DEPTNO"="E1"."DEPTNO" AND "E1"."MAX_SAL">1520
FPD: Following are pushed to having clause of query block V_SUB (#0) <<<<<<<<<<<<<<<<<<<<<< FPD
MAX("E"."SAL")>1520 <<<<<<<<<<<<<<<<<<<<<<
FPD: Considering simple filter push (pre rewrite) in query block V_SUB (#0)
FPD: Current where clause predicates ??
************************************
Cost-based predicate pushdown (JPPD)
************************************
...
FPD: Considering simple filter push in query block V_SUB (#2) <<<<<<<<<<<<<<<<<<<<<< CBPPD
"E"."SAL">1520 <<<<<<<<<<<<<<<<<<<<<<
try to generate transitive predicate from check constraints for query block V_SUB (#2) <<<<<<<<<<<<<<<<<<<<<<
finally: "E"."SAL">1520 <<<<<<<<<<<<<<<<<<<<<<
OJE: Begin: find best directive for query block V_SUB (#2)
OJE: End: finding best directive for query block V_SUB (#2)
Final query after transformations:******* UNPARSED QUERY IS *******
SELECT /*+ QB_NAME ("V_MAIN") */ "D"."DEPTNO" "DEPTNO","D"."DNAME" "DNAME","E1"."MAX_SAL" "MAX_SAL" FROM "SCOTT"."DEPT" "D", (SELECT /*+ QB_NAME ("V_SUB") */
"E"."DEPTNO" "DEPTNO",MAX("E"."SAL") "MAX_SAL" FROM "SCOTT"."EMP" "E" WHERE "E"."SAL">1520 GROUP BY "E"."DEPTNO" HAVING MAX("E"."SAL")>1520) "E1" WHERE "D".
"DEPTNO"="E1"."DEPTNO"
kkoqbc: optimizing query block V_SUB (#2)
-- CBPPD 수행 전에 CVM 에서 Iteration 이 먼저 수행 됨 - CVM(0), CVM(1)
-- 이 경우 Iteration 1, 2 가 없고 CBPPD(0) 과 CBPPD(1) 을 비교 하는 대신 CVM(0) 과 CBPPD(1) 를 비교 하면 됨
Cost-based complex view merging on query block V_MAIN (#1)
CVM: Using search type: linear
CVM: Considering view merging on query block V_MAIN (#1)
CVM: Starting iteration 1, state space = (2) : (0)
CVM: Updated best state, Cost = 5.00
CVM: Starting iteration 2, state space = (2) : (1)
CVM: Not update best state, Cost = 7.01
CVM: Will not merge view V_SUB (#2) <<<<<<<<<<<<<<<<<<<<<< Cost 비교에 의해 버림 받음
예제1(PPU)
-- 인라인뷰 내c.cust_credit_limit <= 조건은 비용이 비싸고 Filter 효과도 없으므로 선 GROUP BY 후 Filter 가 유리 함
SELECT a.cnt
FROM (SELECT COUNT (*) cnt
FROM customers c
WHERE c.cust_credit_limit <= (SELECT MAX (e.cust_credit_limit)
FROM customers e
WHERE e.cust_id < 10000)
AND c.cust_id < 10000
GROUP BY c.cust_credit_limit
ORDER BY c.cust_credit_limit) a
WHERE ROWNUM < 5;
-----------------------------------------+-----------------------------------+
| Id | Operation | Name | Rows | Bytes | Cost | Time |
-----------------------------------------+-----------------------------------+
| 0 | SELECT STATEMENT | | | | 789 | |
| 1 | COUNT STOPKEY | | | | | |
| 2 | VIEW | | 9977 | 253K | 395 | 00:00:05 |
| 3 | SORT GROUP BY | | 9977 | 175K | 395 | 00:00:05 |
| 4 | TABLE ACCESS FULL | CUSTOMERS| 9977 | 175K | 394 | 00:00:05 |
| 5 | SORT AGGREGATE | | 1 | 18 | | |
| 6 | TABLE ACCESS FULL | CUSTOMERS| 9977 | 175K | 394 | 00:00:05 |
-----------------------------------------+-----------------------------------+
Predicate Information:
----------------------
1 - filter(ROWNUM<5)
2 - filter("CUST_CREDIT_LIMIT"<=)
4 - filter("C"."CUST_ID"<10000)
6 - filter("E"."CUST_ID"<10000)
/*+
BEGIN_OUTLINE_DATA
IGNORE_OPTIM_EMBEDDED_HINTS
OPTIMIZER_FEATURES_ENABLE('11.1.0.6')
DB_VERSION('11.1.0.6')
ALL_ROWS
OUTLINE_LEAF(@"SEL$3")
OUTLINE_LEAF(@"SEL$D1D2D72E")
PULL_PRED(@"SEL$1" "A" 1) <<<<<<<<<<<<<<<<<<<<<<
OUTLINE_LEAF(@"SEL$7EB7D0CF")
OUTLINE(@"SEL$2")
OUTLINE(@"SEL$1")
NO_ACCESS(@"SEL$7EB7D0CF" "A"@"SEL$1")
PUSH_SUBQ(@"SEL$3")
FULL(@"SEL$D1D2D72E" "C"@"SEL$2")
FULL(@"SEL$3" "E"@"SEL$3")
END_OUTLINE_DATA
*/
-- 위의 SQL 이 CBQT 에 의해 아래 처럼 재 작성 됨
SELECT a.cnt
FROM (SELECT COUNT (*) cnt, c.cust_credit_limit
FROM customers c
WHERE c.cust_id < 10000
GROUP BY c.cust_credit_limit
ORDER BY c.cust_credit_limit) a
WHERE ROWNUM < 5
AND a.cust_credit_limit <= (SELECT MAX (e.cust_credit_limit)
FROM customers e
WHERE e.cust_id < 10000);
-- 10053 Trace
***********************************
Cost-Based Filter Predicate Pull-Up
***********************************
PPU: Checking validity of pull-up from query block SEL$2 (#2) to query block SEL$1 (#1)
PPU: Pull-up from query block SEL$2 (#2) <<<<<<<<<<<<<<<<<<<<<<
PPU: Using search type: linear <<<<<<<<<<<<<<<<<<<<<<
Predicate pull-up on query block SEL$1 (#1)
PPU: Considering filter predicate pull-up on query block SEL$1 (#1)
PPU: Starting iteration 1, state space = (1) : (0) <<<<<<<<<<<<<<<<<<<<<<
PPU: Pulling up expensive predicates in query block SEL$2 (#2) <<<<<<<<<<<<<<<<<<<<<< SQL 변형 과정이 안보임(변경전)
FPD: Considering simple filter push in query block SEL$1 (#1)
ROWNUM<5
try to generate transitive predicate from check constraints for query block SEL$1 (#1)
finally: ROWNUM<5
FPD: Considering simple filter push in query block SEL$2 (#2)
"C"."CUST_CREDIT_LIMIT"<= (SELECT MAX("E"."CUST_CREDIT_LIMIT") FROM "CUSTOMERS" "E") AND "C"."CUST_ID"<10000
FPD: Considering simple filter push in query block SEL$3 (#3)
"E"."CUST_ID"<10000
try to generate transitive predicate from check constraints for query block SEL$3 (#3)
finally: "E"."CUST_ID"<10000
try to generate transitive predicate from check constraints for query block SEL$2 (#2)
finally: "C"."CUST_CREDIT_LIMIT"<= (SELECT MAX("E"."CUST_CREDIT_LIMIT") FROM "CUSTOMERS" "E") AND "C"."CUST_ID"<10000
PPU: Costing transformed query.
...
PPU: Updated best state, Cost = 789.85
PPU: Starting iteration 2, state space = (1) : (1) <<<<<<<<<<<<<<<<<<<<<<
PPU: Pulling up expensive predicates in query block SEL$2 (#2) <<<<<<<<<<<<<<<<<<<<<< SQL 변형 과정이 잘보임(변경후)
PPU: Added column to select list: CUST_CREDIT_LIMIT
PPU: Exporting predicate #1 to outer query block.
Registered qb: SEL$D1D2D72E 0xe17b784 (PREDICATES REMOVED FROM QUERY BLOCK SEL$2; SEL$1; A 1)
*************************
...
---------------------
QUERY BLOCK SIGNATURE
---------------------
signature (): qb_name=SEL$7EB7D0CF nbfros=1 flg=0
fro(0): flg=1 objn=0 hint_alias="A"@"SEL$1"
FPD: Considering simple filter push in query block SEL$7EB7D0CF (#1)
ROWNUM<5 AND "A"."CUST_CREDIT_LIMIT"<= (SELECT MAX("E"."CUST_CREDIT_LIMIT") FROM "CUSTOMERS" "E")
FPD: Considering simple filter push in query block SEL$3 (#3)
"E"."CUST_ID"<10000
try to generate transitive predicate from check constraints for query block SEL$3 (#3) <<<<<<<<<<<<<<<<<<<<<<
finally: "E"."CUST_ID"<10000 <<<<<<<<<<<<<<<<<<<<<<
try to generate transitive predicate from check constraints for query block SEL$7EB7D0CF (#1)
finally: ROWNUM<5 AND "A"."CUST_CREDIT_LIMIT"<= (SELECT MAX("E"."CUST_CREDIT_LIMIT") FROM "CUSTOMERS" "E")
kkqfppRelFilter: Not pushing filter predicates in query block SEL$D1D2D72E (#2) because predicate is pullup predicate
FPD: Considering simple filter push in query block SEL$D1D2D72E (#2)
"C"."CUST_ID"<10000
try to generate transitive predicate from check constraints for query block SEL$D1D2D72E (#2)
finally: "C"."CUST_ID"<10000
PPU: Costing transformed query.
*************************
First K Rows: Setup begin
kkoqbc: optimizing query block SEL$3 (#3)
...
PPU: Updated best state, Cost = 789.47 <<<<<<<<<<<<<<<<<<<<<< 789.47 < 789.85
PPU: Transferring best state space to preseved query.
PPU: Will pullup predicate #1 (Preserved)
PPU: Transferring best state space to preseved query.
PPU: Will pullup predicate #1 (Original)
kkqctdrvTD-cleanup: transform(in-use=24048, alloc=29396) :
call(in-use=67144, alloc=98240), compile(in-use=213648, alloc=217788), execution(in-use=2848, alloc=4060)
kkqctdrvTD-end:
call(in-use=67144, alloc=98240), compile(in-use=183996, alloc=217788), execution(in-use=2848, alloc=4060)
PPU: Pulling up expensive predicates in query block SEL$2 (#2) <<<<<<<<<<<<<<<<<<<<<<
PPU: Added column to select list: CUST_CREDIT_LIMIT <<<<<<<<<<<<<<<<<<<<<<
PPU: Exporting predicate #1 to outer query block. <<<<<<<<<<<<<<<<<<<<<<
-- 10g 이후 부터 하드파싱 때 CBQT 가 발생 함으로 인해 부담 증폭
-- CBQT(PPU) 처리 과정
-- 1. 변환과정 전의 비용계산
-- 2. 변환과정
-- 3. 변환과정 후의 비용계산
-- 4. 둘중에 비용이 적게 드는 SQL을 선택