Download pdf - Query Transformation

Transcript
Page 1: Query Transformation

Version 변경일자 변경자(작성자) 주요내용

Author 서강혁

Creation Date 2010-02-04

Last Updated

Version

Copyright(C) 2004 Goodus Inc.

All Rights Reserved

Query Transformation 이해

Page 2: Query Transformation

Query Transformation

- - 2

Contents

1. QUERY TRANSFORMATION 이란? ......................................................................................... 3

1.1. 단순 QUERY TRANSFORMATION ............................................................................... 3

1.2. 왜 OPTIMIZER 가 QUERY TRANSFORMATION 을 하는가? ........................................... 4

2. ORACLE OPTIMIZER ............................................................................................................. 5

2.1. OPTIMIZER 기본 구조 ................................................................................................ 5

2.1.1. 질의변환기 .................................................................................................................. 5

2.1.2. 비용산정기(Estimator) ................................................................................................. 6

2.1.3. 실행계획산정기(Plan Generator) ................................................................................ 6

3. Transformation 기법 ............................................................................................................ 6

3.1. ViewMerging ................................................... 오류! 책갈피가 정의되어 있지 않습니다.

3.1.1. ViewMerge case 1 ...................................................................................................... 6

3.1.2. ViewMerge case 2 ...................................................................................................... 8

3.2. Predicate Push ................................................ 오류! 책갈피가 정의되어 있지 않습니다.

3.2.1. Predicate Push Case 1 ................................................................................................ 9

3.2.2. Predicate Push Case 2 .............................................................................................. 10

3.2.3. Predicate Push Case 3 .............................................................................................. 12

3.3. SubQuery Unnesting ....................................... 오류! 책갈피가 정의되어 있지 않습니다.

3.3.1. SubQuery Unnesting case 1 ..................................................................................... 14

3.3.2. SubQuery Unnesting case 2 ..................................................................................... 16

3.4. SubQuery Pushing ........................................... 오류! 책갈피가 정의되어 있지 않습니다.

3.4.1. SubQuery Pushing case 1 ........................................................................................ 18

4. CostBasedQueryTransformation 에 대해서 .......................................................................... 19

Page 3: Query Transformation

Query Transformation

- - 3

굿어스 DB 기술팀은 매월 고객님을 대상으로 기술노트를 발송하고 있습니다.

올해부터는 기존에 발송되고 있는 기술노트 외에 RAC 를 비롯한 RDBMS 성능진단 방법론 및 SQL-

Tuning 을 위한 별도의 기술노트를 함께 발송하고자 합니다.

이번 달은 첫번째로 ORACLE OPTIMIZER 의 QUERY TRANSFORMATION 기법을 다루도록 하겠습니다.

1. QUERY TRANSFORMATION이란? QUERY TRANSFORMATION 은 OPTIMIZER 가 Hard Parsing 을 수행할 때 복잡한 Query 를 단순화 시키기

위해서 수행하는 Query 단순화 과정이라 할 수 있다.

우리가 Tuning 을 하면서 보게되는 실행계획도 모두 QUERY TRANSFORMATION 과정을 거쳐 단순화 된

Query 구문을 OPTIMIZER 가 QUERYBLOCK 단위로 합쳐놓은 결과에 불과하다.

이와 같은 QUERY TRANSFORMATION 의 과정에는 A Between 1 and 100 을 A>=1 and A<=100 와 같이

between ~and 를 and 로 또는 IN 을 OR 로 변환하는 단순한 과정에서부터 우리가 이제부터 살펴볼 4

가지의 변환과정에 이르기 까지 매우 다양하며, 변환과정에 따라 실행계획에 큰 영향을 준다는 점에서

SQL 성능향상 및 SQL-Tuning 을 위해 QUERY TRANSFORMATION 에 대한 이해는 필수적이라 할 수 있

다.

1.1. 단순 QUERY TRANSFORMATION

OPTIMIZER는 사용자의 query안에 Between~and가 있으면 And구문으로, 연산자가 있다면 연산의

Page 4: Query Transformation

Query Transformation

- - 4

결과로, IN절은 OR절로, 부정문은 긍정문으로 Transformation하여 Optimization을 수행한다.

따라서 사용자는 Query 를 작성할 때 이와 같은 OPTIMIZER의 성향을 이해하여 Query를 작성하는 것이

바람직하다.

1.2. 왜 OPTIMIZER가 QUERY TRANSFORMATION을 하는가?

Oracle Optimzer 는 대부분의 경우 최적화된 실행계획을 수립하지만 OPTIMIZER 는 3 개 이상의

Table 간의 Join, InLine View 나 SubQuery 가 포함된 복잡한 Query 에 대해서는 한번에 실행계획을 수립

하지 못하고 QUERYBLOCK 단위로 실행계획을 세운다음 마지막에 이를 하나로 합친다.

그래서 OPTIMIZER 는 복잡한(Complicated) Query 가 들어오면 우선 이것을 최대한 단순화 시키려고 한

다.

예를 들어

SELECT A.*,B.*

From A, (SELECT * FROM B)

Where A.a in (select C.a from C); 라는 Query 가 있다고 하자.

이 Query 는 MainQuery 에 대한 QueryBlock 하나, From 절 이하의 InLine View 에 대한 QUERYBLOCK

하나, 그리고 SubQuery 에 대한 QUERYBLOCK 하나, 이렇게 총 3 개의 QUERYBLOCK 을 가진다.

OPTIMIZER 는 이런 query 가 들어오면 먼저 QUERYBLOCK 단위로 나누어서 각각 실행계획을 수립한 다

음 마지막에 가서 이것을 합친다. 따라서 3 개 QueryBlock 에 대한 실행계획이 만들어 진다..

OPTIMIZER 는 이렇게 분리된 QUERYBLOCK 단위로 실행계획을 세우기 보다는 각각의 QUERYBLOCK 을

묶어서 하나의 집합으로 만든다음 실행계획을 수립하고자 하는 경향이 강하다.

이는 QUERYBLOCK 을 합쳐서 하나의 실행계획을 수립하는 것이 더 좋은 실행계획을 수립할 수 있는 가

능성 또는 경우의 수가 더 많아지기 때문이다.

위의 Query 의 경우 집합 A 와 InLineView 집합 B 가 따로 실행계획을 수립하는 경우에는 집합 A 와 집

합 B 가 별개로 수행되어 집합 B 가 View 로 만들어진 후에야 조인을 하게 되지만, 만약 집합 A 와 집합

B 가 먼저 Join 이 가능하다면-이것이 View Merge 이다- A , B 두집합의 상수 조건으로 집합의 크기를 줄

여서 Join 이 가능하기 때문에 좀 더 다양한 Access Path 나 Join Order , Join Method 를 Optimzer 가 선

택할 수 있게 된다.

즉 OPTIMIZER 가 QUERY TRANSFORMATION 을 수행하는 가장 큰 목적은 QUERY TRANSFORMATION 을

수행한 후

에 OPTIMIZER 는 좀 더 다양한 ACCESS PATH , JOIN ORDER , JOIN METHOD 를 선택할 수 있고, 이것이

최적화된 실행계획을 수립하는데 더욱 도움이 되기 때문이다.

하지만 항상 QUERY TRANSFORMATION(이하 QT) 이후의 실행계획이 최적화 되었다고 장담할 수 있는

가? 그렇지 않다. 왜 그렇지 못한가에 대한 논의도 이후에 같이 다루도록 하겠다.

Page 5: Query Transformation

Query Transformation

- - 5

2. ORACLE OPTIMIZER 이번 기술노트는 QT 에 관련된 내용이므로 Oracle OPTIMIZER 에 대한 상세한 작동원리는 생략하도록 하

겠다. 여기서는 QT 를 이해하기 위한 수준으로만 OPTIMIZER 에 대해 설명하도록 하겠다.

2.1. OPTIMIZER 기본 구조

OPTIMIZER의 기본 구조는 아래와 같다.

OPTIMIZER는 크게 질의변환기(Query Transformer), 비용산정기(Estimator), 실행계획생성기(Plan

Generator)로 나뉘며 각각의 역할은 다음과 같다.

2.1.1. 질의변환기

특정 Query가 Hard Parse가 수행될 경우 이 Query는 문법검사(syntax) 와 의미분석(semantics) 과정을

거친다. 이렇게 Parsed Query는 질의변환기에서

View Merging

Predicate Push

Subquery Unnesting

Subquery Pushing

과 같은 질의 변환을 수행한다. 단순한 QT는 Parsing을 수행하면서 처리되기도 하지만 이해하기 쉽도록

Page 6: Query Transformation

Query Transformation

- - 6

모든 Query의 TRANSFOMATION이 발생하는 영역으로 이해하면 되겠다.

2.1.2. 비용산정기(Estimator)

QT를 통해 단순화된 Query는 QUERYBLOCK단위로 비용산정기에서 Dictionary Table내에 가장 최신의

통계정보를 기반으로 Query 수행에 필요한 예측 비용을 산정한다.

그리고 가장 낮은 비용(Cost)이 드는 실행계획을 선택한다. QUERYBLOCK별로 COST를 산정하고

최적화된 실행계획을 수립하는 곳이 바로 비용산정기이다.

2.1.3. 실행계획산정기(Plan Generator)

QUERYBLOCK단위로 생성된 실행계획은 실행계획산정기 내에서 하나로 합쳐진다.

이때에도 OPTIMIZER는 가장 낮은 비용이 드는 실행계획을 선택하게 된다. 이처럼 QUERYBLOCK단위의

실행계획을 하나로 합쳐 최종실행계획을 수립하는 곳이 실행계획산정기 이다.

3. Transformation 기법 특정 SQL 이 성능이 저하되었다거나 아니면 SQL-Tuning 을 위해서 제일 먼저 확인하는 것은 실행계획이

다. 그리고 정확한 실행계획에 대한 이해는 SQL 과 관련된 문제해결의 출발점이기도 하다.

그러나 그 보다 더 중요한 것은 OPTIMIZER 가 왜 이런 실행계획을 만들었는가? 에 대한 이해이다.

이 원리를 이해하면 대부분의 경우 실행계획에서 어디가 문제이고, 어떻게 개선해 나가면 될 것인지가

자연스럽게 얻어지게 된다.

왜 이런 실행계획이 만들어졌는가에 대한 해답이 바로 QUERY TRANSFORMATION 에 대한 이해에서 출

발한다. 그만큼 QT 는 실행계획과 Tuning 에 중요한 요소이다.

3.1. VIEW MERGING

From 절 이하에 InLine 가 있을 경우 OPTIMIZER 는 이를 어떻게 처리할까? 언뜻 생각하기에는

InLineView 가 괄호로 묶여 있으니까.. 괄호로 묶여있는게 제일 먼저 실행되니까.. InLineView 가 제일 먼

저 실행되지 않을까하고 짐작할 수도 있다. 그러나 OPTIMIZER 는 View 라고 해서 View 자체로 실행계획

을 수립하지 않는다.

괄호를 묶여있는 View 라 하더라도 From 절 이하의 기타 Table 과 Join 처리가 가능하다면

TRANSFORMATION 과정에서 View 를 다른 Table 로 합치는(Merge) 과정을 거쳐 JOIN 으로 처리하게 되

는데 이를 ViewMerging 이라 한다.

3.1.1. ViewMerge case 1

간단한 예를 살펴보겠다.

SELECT A.colA,B.colA,A.colC,B.colC,C.colA,C.colB,C.colC

FROM TAB_A A,TAB_B B

Page 7: Query Transformation

Query Transformation

- - 7

,(SELECT /*+ NO_MERGE */ colA,colB,colC

FROM TAB_C

WHERE colA=100) C

WHERE A.colA=B.colA

AND A.colA=C.colA

AND A.colA=100

테이블 TAB_A,TAB_B 와 InLinewView 인 C 가 있다. 이 쿼리의 실행계획은 NO_MERGE Hint 가 적용되어

Table 과 View 가 Merge 되지 않고 별도로 수행된다.

Rows Row Source Operation

------- ---------------------------------------------------

0 STATEMENT

80916 HASH JOIN (cr=1235 pr=0 pw=0 time=324465 us)

132 TABLE ACCESS BY INDEX ROWID TAB_B (cr=124 pr=0 pw=0 time=1692 us)

134 NESTED LOOPS (cr=6 pr=0 pw=0 time=4152 us)

1 TABLE ACCESS BY INDEX ROWID TAB_A (cr=3 pr=0 pw=0 time=65 us)

1 INDEX RANGE SCAN IDX_TABA_COLA (cr=2 pr=0 pw=0 time=26 us)OF IDX_TABA_COLA

132 INDEX RANGE SCAN IDX_TABB_COLA (cr=3 pr=0 pw=0 time=543 us)OF IDX_TABB_COLA

613 VIEW (cr=1111 pr=0 pw=0 time=13568 us)

613 TABLE ACCESS FULL TAB_C (cr=1111 pr=0 pw=0 time=10471 us)

Table TAB_A 와 TAB_B 가 NL Join 으로 처리되고 나서 View-TAB_C-와 HASH JOIN 처리되는 것을 확인

할 수 있다.

그러나 만약 View Merge 가 일어난다면 실행계획은 아래와 같다.

Rows Row Source Operation

------- ---------------------------------------------------

0 STATEMENT

80916 HASH JOIN (cr=627 pr=0 pw=0 time=244581 us)

613 TABLE ACCESS BY INDEX ROWID TAB_C (cr=361 pr=0 pw=0 time=8690 us)

615 NESTED LOOPS (cr=6 pr=0 pw=0 time=9251 us)

1 TABLE ACCESS BY INDEX ROWID TAB_A (cr=3 pr=0 pw=0 time=64 us)

1 INDEX RANGE SCAN IDX_TABA_COLA (cr=2 pr=0 pw=0 time=24 us)OF IDX_TABA_COLA

613 INDEX RANGE SCAN IDX_TABC_COLA (cr=3 pr=0 pw=0 time=1867 us)OF IDX_TABC_COLA

132 TABLE ACCESS BY INDEX ROWID TAB_B (cr=266 pr=0 pw=0 time=3850 us)

132 INDEX RANGE SCAN IDX_TABB_COLA (cr=134 pr=0 pw=0 time=1723 us)OF IDX_TABB_COLA

먼저 TAB_A 와 TAB_C 가 ViewMerge 되어 NL JOIN 을 수행하고 다음으로 조인의 결과집합과 TAB_B 를

HASH JOIN 하는 것을 확인 할 수 있다. VIEW MERGE 가 되면서 JOIN ORDER 까지 변경된 것을 확인 할

Page 8: Query Transformation

Query Transformation

- - 8

수 있다. 또 ViewMerge 가 되면 실행계획상에 VIEW RowSource 가 나오지 않는다.

3.1.2. ViewMerge case 2

실제 사례로 확인해 보면

SELECT F.USER_NM AS USER_NM

........................

A.PROC_DY AS PROC_DY

FROM CRM_NAN_TRN A,

CRM_NAN_MST B,

(SELECT PGM_ID, COMM_CD, COMM_NM FROM CRM_COM_COD

WHERE PGM_ID = :1 AND GRP_CD = 'ANCU' ) C,

(SELECT PGM_ID, COMM_CD, COMM_NM FROM CRM_COM_COD

WHERE PGM_ID = :2 AND GRP_CD = 'ANST' ) D,

CRM_COM_MST F

WHERE A.PGM_ID = :3

AND A.PGM_ID = B.PGM_ID(+)

AND A.PGM_ID = C.PGM_ID(+)

AND A.QST_TYP_CD = C.COMM_CD(+)

AND A.PGM_ID = D.PGM_ID(+)

AND A.PROC_STS = D.COMM_CD(+)

AND A.QST_RESD_NO = B.RESD_NO(+)

AND A.PGM_ID = F.PGM_ID(+)

AND A.REGR_ID = F.USER_ID(+)

AND A.REQ_DY BETWEEN :4 AND :5

AND A.ORD_QST_FG = '2'

AND A.QST_FG_CD = '01'

ORDER BY A.DY DESC

Rows Row Source Operation

------- ---------------------------------------------------

0 STATEMENT

1 FILTER (cr=602814 pr=0 pw=0 time=144586301 us)

1 NESTED LOOPS OUTER (cr=602814 pr=0 pw=0 time=144586299 us)

2 NESTED LOOPS OUTER (cr=17 pr=0 pw=0 time=238 us)

2 NESTED LOOPS OUTER (cr=17 pr=0 pw=0 time=229 us)

Page 9: Query Transformation

Query Transformation

- - 9

2 NESTED LOOPS OUTER (cr=11 pr=0 pw=0 time=187 us)

2 TABLE ACCESS BY INDEX ROWID CRM_NAN_TRN (cr=7 pr=0 pw=0 time=146 us)

2 INDEX RANGE SCAN DESCENDING CRM_NAN_TRN_PK (cr=4 pr=0 pw=0 time=40 us)OF

0 TABLE ACCESS BY INDEX ROWID CRM_COM_MST (cr=4 pr=0 pw=0 time=36 us)

0 INDEX UNIQUE SCAN CRM_COM_MST_PK (cr=4 pr=0 pw=0 time=34 us)OF

2 TABLE ACCESS BY INDEX ROWID CRM_COM_COD (cr=6 pr=0 pw=0 time=34 us)

2 INDEX UNIQUE SCAN CRM_COM_COD_PK (cr=4 pr=0 pw=0 time=21 us)OF CRM_COM_COD_PK

0 TABLE ACCESS BY INDEX ROWID CRM_COM_COD (cr=0 pr=0 pw=0 time=6 us)

0 INDEX UNIQUE SCAN CRM_COM_COD_PK (cr=0 pr=0 pw=0 time=3 us)OF CRM_COM_COD_PK

0 TABLE ACCESS BY INDEX ROWID CRM_NAN_MST (cr=602805 pr=0 pw=0 time=144586232 us)

0 INDEX RANGE SCAN CRM_NAN_MST_PK (cr=602805 pr=0 pw=0 time=144586229 us)OF

From 절 이하에 InLineView C, D 가 있다. 실행계획상에서 View Merge 가 일어나지 않았다면 C, D 는

VIEW 의 형식으로 나타나야 한다.

그러나 실제 실행계획상에는 C 와 D 가 From 절 이하의 Table 들과 NL Join 으로 처리되는 것을 확인할

수 있다.

3.2. PREDICATE PUSHING

View Merge 은 매우 광범위하게 일어나지만 모든 InLineView 가 Merge 될 수 있는 건 아니다.

InLineView 내부에 ROWNUM , DISTINCT , GROUP BY - aggregate function 은 경우에 따라 ViewMerge 되

긴 하지만 대부분 merge 할 수 없다.- , SET OPERATOR- UNION,UNION ALL,MINUS,INTERSECT- 가 있으

면 View Merge 는 일어나지 않는다.

이때에는 실행계획에 View row source 가 나타난다. 하지만 이런 경우에도 OPTIMIZER 는 메인 query 의

where 조건절(Predicate)에 있는 조건값을 InLiewView 내부로 가져와 View 의 집합을 줄이려 한다.

이것을 Predicate Pushing 이라 한다.

3.2.1. Predicate Push Case 1

예를 들어보자.

SELECT A.colA,B.colA,A.colC,C.colC,C.colA,B.SUM_COLB

FROM TAB_A A,TAB_C C

,(SELECT colA,SUM(colB) SUM_COLB

FROM TAB_B

GROUP BY colA) B

WHERE A.colA=B.colA AND A.colA=C.colA

AND A.colA=10;

Page 10: Query Transformation

Query Transformation

- - 10

---------------------------------------------------------------------------------------------------

| Id | Operation | Name | E-Rows | Cost (%CPU)| A-Rows | Buffers |

---------------------------------------------------------------------------------------------------

|* 1 | HASH JOIN | | 1572 | 229 (3)| 2582 | 1386 |

| 2 | TABLE ACCESS BY INDEX ROWID | TAB_A | 1 | 2 (0)| 2 | 212 |

| 3 | NESTED LOOPS | | 1 | 113 (0)| 6 | 210 |

| 4 | VIEW | | 1 | 111 (0)| 2 | 206 |

| 5 | HASH GROUP BY | | 1 | 111 (0)| 2 | 206 |

| 6 | TABLE ACCESS BY INDEX ROWID| TAB_B | 123 | 111 (0)| 238 | 206 |

|* 7 | INDEX RANGE SCAN | IDX_TABB_COLA | 124 | 1 (0)| 238 | 4 |

|* 8 | INDEX RANGE SCAN | IDX_TABA_COLA | 1 | 1 (0)| 2 | 4 |

|* 9 | TABLE ACCESS FULL | TAB_C | 1572 | 115 (5)| 2582 | 1174 |

---------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):

---------------------------------------------------

1 - access("A"."COLA"="C"."COLA")

7 - access("COLA"=10)

8 - access("A"."COLA"=10)

9 - filter("C"."COLA"=10)

위의 Query 는 InLineView 내부에 aggregate Function 이 존재하는 경우이다. 이런 경우 ViewMerge 가

일어나지 못하는게 대부분이다. 그래서 실행계획상에 VIEW row source 를 확인할 수 있다.

그러면 InLineView 는 View 집합을 만들기 위해 InLineView 내부에서 TAB_B TABLE 전체에 대한 GROUP

BY 를 했을까? 그렇지 않다.

Where 조건절을 보면 ResultSet 에서 필요한 값은 COLA=10 인 것만 필요하다. InLineView 내부에서 전

체 집합에 대한 Grouping 을 수행할 필요가 없다. COLA=10 인 값에 대한 Grouping 만 수행하면 된다.

Predicate Information 를 확인해 보면 InLiewView 안에서 어떤 값에 대한 Grouping 이 일어났는지 알 수

있다.

7 - access("COLA"=10) 처럼 OPTIMIZER 는 InLineView 내부에서 전체 Grouping 을 수행한 것이 아니라 COLA=10

인 컬럼에 대한 Grouping 만을 수행한 것을 확인할 수 있다.

3.2.2. Predicate Push Case 2

다른 예를 들어보자.

SELECT /*+ NO_PUSH_PRED(B) */ A.colB,B.colB,A.colC,B.colC

FROM TAB_A A

,(SELECT colB,colC FROM TAB_B

UNION ALL

SELECT colB,colC FROM TAB_C) B

Page 11: Query Transformation

Query Transformation

- - 11

WHERE A.colB=B.colB

AND A.colA=1

이렇게 SET OPERATOR 로 묶여 있는 InLineView 도 View Merge 를 수행할 수 없다. 그러면 OPTIMIZER 는 QT 과정

에서 PREDIATE PUSH 를 수행하려 할 것이다.

그런데 위의 구문은 NO_PUSH_PRED Hint 를 사용하여 강제로 MAIN 의 조건이 InLineView 안으로 PUSH 되지 못하

도록 하였다.

이럴 경우 실행계획은 아래에서 확인 할 수 있는 것처럼 A.colA=1 조건이 InLineView 내부에 전혀 영향을 주지 못

해서 TAB_B 와 TAB_C 가 FULL SCAN 하는 것을 확인 할 수 있다.

--------------------------------------------------------------------------------------------

| Id | Operation | Name | E-Rows | Cost (%CPU)| A-Rows | Buffers |

-----------------------------------------------------------------------------------------------

|* 1 | HASH JOIN | | 2439 | 236 (6)| 42 | 3012 |

| 2 | TABLE ACCESS BY INDEX ROWID| TAB_A | 1 | 2 (0)| 3 | 9 |

|* 3 | INDEX RANGE SCAN | IDX_TABA_COLA | 1 | 1 (0)| 3 | 6 |

| 4 | VIEW | | 246K| 229 (4)| 740K| 3003 |

| 5 | UNION-ALL | | | | 740K| 3003 |

| 6 | TABLE ACCESS FULL | TAB_B | 122K| 115 (5)| 370K| 1503 |

| 7 | TABLE ACCESS FULL | TAB_C | 123K| 115 (5)| 370K| 1500 |

-----------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):

---------------------------------------------------

1 - access("A"."COLB"="B"."COLB")

3 - access("A"."COLA"=1)

하지만 PREDICATE PUSH 가 될 경우 실행계획은 아래와 같이 메인 조건이 InLineView 내부로 Push 되어 InLineView

내부의 집합을 효율적으로 줄이는 것으로 실행계획이 변경된다.

-------------------------------------------------------------------------------------------------

| Id | Operation | Name | E-Rows | Cost (%CPU)| A-Rows | Buffers |

-------------------------------------------------------------------------------------------------

| 1 | NESTED LOOPS | | 2439 | 19 (0)| 14 | 23 |

| 2 | TABLE ACCESS BY INDEX ROWID | TAB_A | 1 | 2 (0)| 1 | 4 |

|* 3 | INDEX RANGE SCAN | IDX_TABA_COLA | 1 | 1 (0)| 1 | 3 |

| 4 | VIEW | | 1 | 17 (0)| 14 | 19 |

| 5 | UNION ALL PUSHED PREDICATE | | | | 14 | 19 |

| 6 | TABLE ACCESS BY INDEX ROWID| TAB_B | 13 | 14 (0)| 12 | 15 |

|* 7 | INDEX RANGE SCAN | IDX_TABB_COLB | 13 | 1 (0)| 12 | 3 |

| 8 | TABLE ACCESS BY INDEX ROWID| TAB_C | 2 | 3 (0)| 2 | 4 |

|* 9 | INDEX RANGE SCAN | IDX_TABC_COLB | 2 | 1 (0)| 2 | 2 |

Page 12: Query Transformation

Query Transformation

- - 12

-------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):

---------------------------------------------------

3 - access("A"."COLA"=1)

7 - access("COLB"="A"."COLB")

9 - access("COLB"="A"."COLB")

3.2.3. Predicate Push Case 3

실제 업무사례를 통해 살펴보면 유사한 경우를 확인할 수 있다.

SELECT A.PGM_ID AS PGM_ID ,

A.MBR_ID AS mbr_id ,

...............................................

E.MBR_IP AS mbr_ip,

TO_CHAR(E.LOGIN_DT, 'YYYY-MM-DD HH24:MI:SS') AS login_dt

FROM MBR_MASTER A,

MBR_MBRETC B,

EPS_MEMBER C,

EPS_SITCOP D,

(SELECT X.EPS_MBR_ID, X.LOGIN_HIST_ID, X.MBR_IP, X.LOGIN_DT

FROM EPS_MBRLGN X, (SELECT EPS_MBR_ID,

MAX(LOGIN_HIST_ID) AS LOGIN_HIST_ID

FROM EPS_MBRLGN

GROUP BY EPS_MBR_ID) Y

WHERE X.EPS_MBR_ID = Y.EPS_MBR_ID

AND X.LOGIN_HIST_ID = Y.LOGIN_HIST_ID ) E,

KR_BIZ_OLD F

WHERE A.PGM_ID = B.PGM_ID(+)

AND A.MBR_ID = B.MBR_ID(+)

AND A.PGM_ID = C.PGM_ID

AND A.MBR_ID = C.MBR_ID

AND C.JOIN_SITE_ID = D.SITE_ID(+)

AND C.EPS_MBR_ID = E.EPS_MBR_ID(+)

AND C.OLD_JOIN_SITE_ID = F.BIZID(+)

AND D.DATA_STS(+) = 'A'

AND A.PGM_ID = :1

AND A.MBR_ID = :2

Page 13: Query Transformation

Query Transformation

- - 13

박스안의 VIEW 가 실제 SQL 에서 InLineView 에 해당하는 구문이다. 위의 경우는 InLineView 안에 Group

by 구문이 있어 VIEW MERGE 가 이뤄지지 못했고 또한 PREDIATE PUSH 도 수행되지 않아 VIEW 내의 테

이블이 INDEX FAST FULL SCAN 과 TABLE FULL SCAN 으로 처리되고 있는 것을 보여주고 있다.

이 SQL 이 PREDIATE PUSH 로 QT 가 일어난 경우 실행계획은 아래와 같다.

Page 14: Query Transformation

Query Transformation

- - 14

SubQuery 가 Predicate Push 되었음을 실행계획상에서도 확인할 수 있다.

3.3. SUBQUERY UNNESTING

SQL 내부에 subquery 가 있다면 FILTER , CONCATENATION , INLIST operation 으로 풀리는 것이

일반적이라고 생각한다. 그러나 MainQuery 와 SubQuery 가 Join 처리 가능하다면 OPTIMIZER 는

subquery 를 main query 와 동등한 위치에서 Join 으로 처리한다. 이를 Subquery Unnesting 이라 한다.

3.3.1. SubQuery Unnesting case 1

예를 들어보자

SELECT A.colA,B.colA,A.colC,B.colC

FROM TAB_A A,TAB_B B

WHERE A.colA=B.colA

AND A.colA IN (SELECT /*+ NO_UNNEST NO_PUSH_SUBQ */ C.colA FROM TAB_C C)

AND A.ColA=1

NO_UNNEST 는 SubQuery UnNest 가 안되도록 NO_PUSH_SUBQ 는 SubQuery Push 가 안되도록

처리하는 Hint 이다.

Page 15: Query Transformation

Query Transformation

- - 15

이럴경우 SubQuery 가 처리되는 방식은 FILTER 일 경우가 대부분이다.

실행계획을 살펴보면

-------------------------------------------------------------------------------------------------

| Id | Operation | Name | E-Rows | Cost (%CPU)| A-Rows | Buffers |

-------------------------------------------------------------------------------------------------

|* 1 | FILTER | | | | 63 | 72 |

| 2 | TABLE ACCESS BY INDEX ROWID | TAB_B | 123 | 111 (0)| 63 | 70 |

| 3 | NESTED LOOPS | | 123 | 113 (0)| 65 | 11 |

| 4 | TABLE ACCESS BY INDEX ROWID| TAB_A | 1 | 2 (0)| 1 | 4 |

|* 5 | INDEX RANGE SCAN | IDX_TABA_COLA | 1 | 1 (0)| 1 | 3 |

|* 6 | INDEX RANGE SCAN | IDX_TABB_COLA | 123 | 1 (0)| 63 | 7 |

|* 7 | INDEX RANGE SCAN | IDX_TABC_COLA | 2 | 1 (0)| 1 | 2 |

-------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):

---------------------------------------------------

1 - filter( IS NOT NULL)

5 - access("A"."COLA"=1)

6 - access("B"."COLA"=1)

7 - access("C"."COLA"=:B1)

JOIN 되는 두 테이블 TAB_A 와 TAB_B 는 JOIN 조건 이외에 상수조건인 COLA=1 을 공유하지만

SubQuery 는 JOIN 후에 MinQuery 에서 넘어오는 :B1 바인드 변수를 받아 FITER 로 처리하는 것을 확인

할 수 있다.

이 Query 가 SUBQUERY UnNest 되면 OPTIMIZER 는 SubQuery 를 MainQuery 와 동등한 위치에서

JOIN 처리한다.

확인해 보도록 하자.

-------------------------------------------------------------------------------------------------

| Id | Operation | Name | E-Rows | Cost (%CPU)| A-Rows | Buffers |

-------------------------------------------------------------------------------------------------

| 1 | TABLE ACCESS BY INDEX ROWID | TAB_B | 123 | 111 (0)| 63 | 72 |

| 2 | NESTED LOOPS | | 123 | 114 (0)| 65 | 13 |

| 3 | NESTED LOOPS SEMI | | 1 | 3 (0)| 1 | 6 |

| 4 | TABLE ACCESS BY INDEX ROWID| TAB_A | 1 | 2 (0)| 1 | 4 |

|* 5 | INDEX RANGE SCAN | IDX_TABA_COLA | 1 | 1 (0)| 1 | 3 |

|* 6 | INDEX RANGE SCAN | IDX_TABC_COLA | 834 | 1 (0)| 1 | 2 |

|* 7 | INDEX RANGE SCAN | IDX_TABB_COLA | 123 | 1 (0)| 63 | 7 |

-------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):

---------------------------------------------------

5 - access("A"."COLA"=1)

6 - access("C"."COLA"=1)

filter("A"."COLA"="C"."COLA")

7 - access("B"."COLA"=1)

동일 구문에서 hint 를 모두 제거하면 SubQuery UnNesting 된 실행계획이 나온다. 먼저 Table Access

순서를 살펴보면 TBA_A -> TBA_C -> TBA_B 순이다.

SubQuery UnNesting 이 되면서 Access 되는 Table 의 순서도 바뀔 수 있다는 것을 확인 할 수 있다.

Page 16: Query Transformation

Query Transformation

- - 16

또한 TAB_A 와 SubQuery TABLE 인 TAB_C 가 SEMI JOIN 으로 처리된 것도 확인할 수 있다. 그리고 상수

조건인 TAB_A.colA=1 인 조건을 TAB_B 뿐만 아니라 TAB_C 까지도 공유하고 있는 것을 Predicate

Information 통해 확인할 수 있다.

3.3.2. SubQuery Unnesting case 2

실무사례 CASE 를 보면

SELECT A.CRD_NO ,

............................

C.RESD_NO AS RESD_NO

FROM CRD_AFTISS A ,

CRD_MASTER B ,

MBR_MASTER C ,

JON_JONCRD D ,

MBR_MBRETC E

WHERE A.PGM_ID = :1

AND A.CRD_ISSU_DY BETWEEN :2 AND :3

AND A.MANUF_FG IN ('N','F')

AND A.PGM_ID = B.PGM_ID

.................................

AND D.WORK_FG = :6

AND A.CRD_NO NOT IN (SELECT CRD_NO

FROM CRD_REMAKE

WHERE REG_DY BETWEEN to_char(to_date(:7, 'YYYYMMDD') - 3, 'YYYYMMDD') and :8

AND REGR_ID IN ('ARS','IVR')

AND PGM_ID = :9 )

위의 SubQuery 가 UnNesting 이 되었을때와 그렇지 못한 경우의 실행계획을 확인할 수 있다.

Page 17: Query Transformation

Query Transformation

- - 17

박스부분이 SubQuery 가 처리된 실행계획이다. 이 부분은 보는 바와 같이 FILTER 로 처리되었다.

MainQuery 의 결과 집합을 가지고 SubQuery 에서 하나씩 하나씩 처리하면서 Filtering 을 한 것이다.

하지만 동일한 쿼리가 아래를 보면 ANSI NL JOIN 으로 처리된 것을 확인할 수 있다.

Page 18: Query Transformation

Query Transformation

- - 18

3.4. SUBQUERY PUSHING

SubQuery 내부에 View Merge 와 마찬가지로 ROWNUM, DISTINCT, GROUP BY, SET OPERATOR 등이 있

으면 Subquery Unnesting 은 불가능한 경우가 많다.

OPTIMIZER 는 이런 경우 SubQuery 내부의 Table 을 Main Query 로 이동하여 table 의 Driving 순서를

제어하기도 한다. 이것을 subquery Pushing 이라 한다.

3.4.1. SubQuery Pushing case 1

우선 SAMPL TABLE 의 COLA 컬럼의 분포도를 살펴보면

TAB_A 가 제일 좋고 TAB_C 가 가장 나쁘다.

SELECT A.colA,C.colA,A.colC,C.colC

FROM TAB_A A,TAB_C C

WHERE A.colA=C.colA

AND A.colA IN (SELECT /*+ NO_UNNEST NO_PUSH_SUBQ */ B.colA

FROM TAB_B B

WHERE colA=1 GROUP BY B.colA)

AND A.ColA=1

위는 colA 의 분포가 가장 좋은 TAB_A 와 가장 나쁜 TAB_C 가 JOIN 하고 분포다고 중간인 TAB_B 가

SUBQUERY 로 만들어진 SQL 이다.

만약 SubQuery UnNesting 이 안되는 상황이라면 위의 Query 는 어떤 실행계획을 가질까?

------------------------------------------------------------------------------------------------

| Id | Operation | Name | E-Rows | Cost (%CPU)| A-Rows | Buffers |

------------------------------------------------------------------------------------------------

|* 1 | FILTER | | | | 682 | 549 |

|* 2 | HASH JOIN | | 1260 | 117 (5)| 682 | 547 |

| 3 | TABLE ACCESS BY INDEX ROWID| TAB_A | 1 | 2 (0)| 1 | 3 |

|* 4 | INDEX RANGE SCAN | IDX_TABA_COLA | 1 | 1 (0)| 1 | 2 |

|* 5 | TABLE ACCESS FULL | TAB_C | 1260 | 115 (5)| 682 | 544 |

|* 6 | FILTER | | | | 1 | 2 |

| 7 | SORT GROUP BY NOSORT | | 1 | 1 (0)| 1 | 2 |

|* 8 | INDEX RANGE SCAN | IDX_TABB_COLA | 125 | 1 (0)| 63 | 2 |

Page 19: Query Transformation

Query Transformation

- - 19

------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):

---------------------------------------------------

1 - filter( IS NOT NULL)

2 - access("A"."COLA"="C"."COLA")

4 - access("A"."COLA"=1)

5 - filter("C"."COLA"=1)

6 - filter("B"."COLA"=:B1)

8 - access("COLA"=1)

실행계획을 보면 TAB_A 와 TAB_C 가 HASH JOIN 하고 그 결과집합으로 SubQuery 와 FILTER 처리하는 것

을 알 수 있다.

만약 OPTIMIZER 가 TAB_C 보다 TAB_B 의 ResultSet 이 작고 TAB_A 와 TBA_B 를 먼저 결합하는 것이 더

유리하다고 판단하면 실행계획은 아래와 같이 나타난다.

-----------------------------------------------------------------------------------------------

| Id | Operation | Name | E-Rows | Cost (%CPU)| A-Rows | Buffers |

-----------------------------------------------------------------------------------------------

|* 1 | HASH JOIN | | 63 | 117 (5)| 682 | 549 |

| 2 | TABLE ACCESS BY INDEX ROWID| TAB_A | 1 | 2 (0)| 1 | 5 |

|* 3 | INDEX RANGE SCAN | IDX_TABA_COLA | 1 | 1 (0)| 1 | 4 |

|* 4 | FILTER | | | | 1 | 2 |

| 5 | SORT GROUP BY NOSORT | | 1 | 1 (0)| 1 | 2 |

|* 6 | INDEX RANGE SCAN | IDX_TABB_COLA | 125 | 1 (0)| 63 | 2 |

|* 7 | TABLE ACCESS FULL | TAB_C | 1260 | 115 (5)| 682 | 544 |

-----------------------------------------------------------------------------------------------

SubQuery 집합 TAB_B 가 TAB_A 와 TAB_C 사이에 들어가 테이블이 JOIN 되기 전에 미리 TAB_A 에 대한

결과집합을 만드는데 먼저 사용되는 것이다.

이렇게 OPTIMIZER 는 자신이 판단하기에 집합의 크기를 효과적으로 줄일 수 있으면 SUBQUERY 를 메인

쿼리 집합사이에 가지고 와서 테이블의 ACCESS 순서를 제어하기도 한다.

4. CostBasedQueryTransformation에 대해서 이번 기술노트는 QueryTransformation 을 통한 성능향상이 주제가 아니다. QueryTransformation 기법 자

체가 주제이다. 그래서 일부러 10046 trace 의 결과는 포함하지 않았다. 실제 위에서 예로 든것들 중에는

QT 이후에 성능이 더 나빠진 것도 있다.

여기서 한가지 오해를 풀어야 할 것 같다. 그것은 QT 를 수행하면 당연히 성능이 개선될 것이라는 믿음

Page 20: Query Transformation

Query Transformation

- - 20

이다. 하지만 항상 그런 것은 아니다. 오히려 QT 후에 성능이 떨어지는 경우가 허다하다.

어째서 이런 현상이 일어나는 것일까? 이에 대해서는 oracle 9i 와 10g 를 구분해서 언급해야 한다.

우선 "_OPTIMIZER_push_pred_cost_based” Hidden Parameter 에 대한 정확한 이해가 필요한다.

DEFAULT VALUE 는 9i FALSE 10g TRUE 이다.

이 Hidden 은 QueryTransformation 을 수행할 때 COST 기반으로 할것인가(TRUE), RULE 기반으로 할 것인

가(FALSE)를 결정하는 파라미터이다.

그러니까 9i 에서는 실행계획은 통계정보가 있다면 CBO 로 만들어지지만 QT 는 RULE 기반으로 이뤄진다

는 뜻이 된다. 다시 말하면 QueryTransformation 과정에서는 QT 를 하는 것이 더 유리한지/불리한지를

따지지 않고 QT 를 할 수 있는 조건만 되면 무조건 해버린다는 것이다.

하지만 10g 에서는 QT 의 과정도 COST 를 고려하여, QT 를 수행하는 것이 더 효과적일 경우에만 수행하

겠다는 것이다.

하지만 10g 에서 실제 QT 가 성능이 개선되는 경우에만 수행이 되는가? 에 대한 답은 아직까지 회의적

이다. 여전히 QT 를 통한 성능향상은 반은 맞고 반은 틀리다.

이제까지 실행계획의 최적화 과정에서 일어나는 QUERY TRANSFORMATION 기법에 대해 알아보았다.

QUERY TRANSFORMATION 은 가치중립적이고 여전히 불안하다.

그러나 실행계획에 대한 정확한 이해와 정교한 SQL-Tuning 을 위해서는 QUERY TRANSFORMAION 에

대한 이해는 아무리 강조해도 지나치지 않다.

다음 기술노트에서는 실제 QT 로 발생하는 성능 저하 원인과 Tuning 사례에 대해 좀 더 자세히 살펴보

기로 하겠다.


Recommended