3.A Cost Based Query Transformation 이란 무엇인가

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;

  • 위의 예제 SQL을 어떻게 변환 시킬 것인지 결정 하기 위해서는 (인라인뷰 변환 후 조인 OR 서브쿼리 체크조건) 아래와 같이 알아야 할 Factor 들이 너무 많으므로 Transformer(Logical Optimizer)는 Cost Estimator(Physical Optimizer)에게 원본/변환 SQL 의 Cost 를 요청 후 낮은 Cost 를 가진 SQL을 선택 한다.
    • TAB1 건수, TAB1.COL5 인덱스 여부 및 Clustering Factor 및 TAB1.COL5 = 4 데이터 분포
    • TAB2 건수, TAB2.COL3 = 1 와 TAB3.COL4 = 2 의 데이터 분포 (좋은 조건)
    • 서브쿼리 와 TAB1 중 Drving 집합에 적절한 것...
  • Cost Estimator 는 완벽한가?
    • 복잡한 SQL, 불완전 통계정보 등의 문제로 Cost Estimator 는 얼마든지 잘못된 판단할 수 있으며 이 불완전성 때문에 튜너가 필요 함
  • CBQT를 배워야 하는 이유
    • CBQT 의 허와 실을 제대로 알아야만 옵티마이저에게 상황에 맞는 방향성을 제시할 수 있다
    • CBQT, HQT 를 알지 못하여 튜닝에 실패하고 Optimizer 에게 책임을 돌리면 안된다

3.B Search Type 과 Iteration 이란 무엇인가

Search Type

변환 가능한 경우의 수를 어디까지 고려할 것인지의 정도(Level)를 설명한 것

  • 서브쿼리가 2개(SUBQ1, SUBQ2)인 SQL의 예
케이스경우의수설명예제표시법
CASE1SUBQ1 Unnest, SUBQ2 Unnest모두 Unnesting 됨(1,1)
CASE2SUBQ1 Unnest, SUBQ2 No UnnestSUBQ1만 Unnesting 됨(1,0)
CASE3SUBQ1 No Unnest, SUBQ2 UnnestSUBQ2만 Unnesting 됨(0,1)
CASE4SUBQ1 No Unnest, SUBQ2 No Unnest모두 변화 없음(0,0)
  • Search Type (_optimizer_cost_based_transformation)
특성Search Type설명
가장 강력한Exhaustive위의 예에서 4가지 경우를 모두 고려 하여 Cost 가 가장 저렴한 SQL을 선택
순차적 비교Linear기본값, 위의 예에서 순차적으로 Unnesting 고려(SUBQ1 확정 후 SUBQ2 확정)

※ 나머지 Iterative, Two_Pass, On, Off 는 4장 에서 설명

Iteration

변환이 수행된 경우와 수행되지 않은 경우의 Cost를 비교하기 위한 경우의 수

  • 서브쿼리가 2개(SUBQ1, SUBQ2)인 SQL의 예
케이스예제표시법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

3.1 CBPPD* (Cost Based Predicate Push Down) : Complex View 에 Filter 를 밀어 넣어라

  • HQT 의 FPD(Filter Push Down) Upgrade 버전
  • _optimizer_enhanced_filter_push = true

예제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 비교에 의해 버림 받음

3.2 PPU (Predicate Pull Up) : 비용이 많이 드는 조건절을 뷰 외부로 이동시켜라

  • 인라인뷰 내 여러 조건 중 비용이 비싸지만 Filter 효과가 거의 없는 경우 해당 조건을 인라인뷰 바깥으로 빼는 경우
  • _optimizer_filter_pred_pullup = true
  • PULL_PRED / NO_PULL_PRED
  • 제약조건
    1. 인라인뷰 필수
    1. 인라인뷰 바깥 쪽에서 ROWNUM 사용
    1. View Merging 이 없어야 함 (아래 예제1 에서는 GROUP BY + ORDER BY 가 막아주는 역할을 함)
    1. 비용이 많이 드는 Filter 필수 (서브쿼리 내 집합함수, 사용자 정의함수)

예제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을 선택