반응형

# DBMS_XPLAN

[실행 계획 예측]
DBMS_XPLAN Package 는 Oracle 9i 에서 소개되었으며 Version Up 에 따라 기능이 점점 확장되고 있다.
Package 이름이 암시하는 것처럼, 이 Package 의 기본 목적은 실행 계획의 예측을 보여주는 것이다.

- 기본적인 사용법

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
SCOTT@ora10g>drop table t1 purge;
 
 
Table dropped.
 
SCOTT@ora10g>create table t1(c1 int, c2 char(10));
 
Table created.
 
SCOTT@ora10g>insert into t1
  select level, 'dummy'
  from dual
  connect by level commit;
 
Commit complete.
 
SCOTT@ora10g>create index t1_n1 on t1(c1);
 
Index created.


EXPLAIN PLAN 명령과 DBMS_XPLAN.DISPLAY 호출의 조합으로 실행 계획을 보여준다.
가장 기본적인 방법은 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
SCOTT@ora10g>explain plan for
  select *
  from t1
  where c1 = 1 and c2 = 'dummy'
  5  ;
 
Explained.
 
SCOTT@ora10g>select * from table(dbms_xplan.display);
 
--------------------------------------------------------------------------
| Id  | Operation                   | Name  | Rows  | Bytes | Cost (%CPU)|
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |       |     1 |    25 |     2   (0)|
|*  1 |  TABLE ACCESS BY INDEX ROWID| T1    |     1 |    25 |     2   (0)|
|*  2 |   INDEX RANGE SCAN          | T1_N1 |     1 |       |     1   (0)|
--------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   1 - filter("C2"='dummy')
   2 - access("C1"=1)
 
Note
-----
   - dynamic sampling used for this statement
 
18 rows selected.


# DBMS_XPLAN.DISPLAY Function 은 기본적을 다음과 같은 세 가지 종류의 값을 보여준다.

    * Row Source Operation 을 보여준다.
       위의 예에서는 Index t1_n1 을 INDEX RANGE SCAN 으로 Access 하는 실행 계획이 수립될 것임을 보여준다.

    * Predicate 정보를 보여준다.
      2번 Operation, 즉 Index t1_n1 에 대한 Range Scan 단계에서는 access("C1"=1) Predicate 가 사용되었다.
      Index Access 를 통해서 걸러진 Data 는 1번 단계, 즉 TABLE ACCESS BY INDEX ROWID Operation 에서 filter("C2"='dummy') Predicate 를 이용해 다시 Filtering 된다.
      Predicate 정보는 이를 이해하지 못하면 Execution Plan 의 절반 밖에 이해하지 못한다고 할 정도로 중요한 정보이다.

    * Note 정보를 통해 부가적으로 필요한 정보를 제공한다.
       이 예제에서는 Dynamic Sampling 이 사용되었음을 알려 준다.
       Oracle 10g 에서는 통계 정보가 없는 Table 에 대해서 Dynamic Sampling 을 수행한다.


# Access Predicate 와 Filter Predicate 의 차이는 다음과 같다.

    * Access Predicate 는 Access Type을 결정하는데 사용되는 Predicate(조건)를 의미한다.
       더 정확하게 말하면 실제 Block 을 읽기 전에 어떤 방법으로 Block 을 읽을 것인가를 결정한다는 의미이다.
       따라서 Index Lookup 이나 Join 등은 Access Predicate 로 표현된다.

    * Filter Predicate 는 실제 Block 을 읽은 후 Data 를 걸러 내기 위해 사용되는 Predicate(조건)을 의미한다.

# Access Predicate 와 Filter Predicate 가 표현되는 방식에 대한 정확한 이해는 실행 계획 해석에 있어 필수적인 지식이다.
   다음 예제를 보면 Access Predicate 와 Filter Predicate 가 어떻게 표현되는지 잘 알 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
SCOTT@ora10g>create table t1(c1 int, c2 int);
 
Table created.
 
SCOTT@ora10g>create table t2(c1 int, c2 int);
 
Table created.
 
SCOTT@ora10g>create index t1_n1 on t1(c1);
 
Index created.
 
SCOTT@ora10g>create index t1_n2 on t2(c1);
 
Index created.
 
SCOTT@ora10g>insert into t1 select level, level
  from dual
  connect by level insert into t2 select level, level
  from dual
  connect by level explain plan for
  select /*+ use_nl(t1 t2) */
  3  *
  from t1, t2
  where t1.c1 = t2.c1
  and   t1.c2 = 1
  7  ;
 
Explained.
 
SCOTT@ora10g>select * from table(dbms_xplan.display);
 
--------------------------------------------------------------------------
| Id  | Operation                   | Name  | Rows  | Bytes | Cost (%CPU)|
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |       |     1 |    52 |     5   (0)|
|   1 |  TABLE ACCESS BY INDEX ROWID| T2    |     1 |    26 |     2   (0)|
|   2 |   NESTED LOOPS              |       |     1 |    52 |     5   (0)|
|*  3 |    TABLE ACCESS FULL        | T1    |     1 |    26 |     3   (0)|
|*  4 |    INDEX RANGE SCAN         | T1_N2 |     1 |       |     1   (0)|
--------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   3 - filter("T1"."C2"=1)
   4 - access("T1"."C1"="T2"."C1")


반면 Hash Join 에서는 Join 단계(1번)에서 Access Predicate 정보가 출력된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
SCOTT@ora10g>explain plan for
  select /*+ use_hash(t1 t2) */
  3  *
  from t1, t2
  where t1.c1 = t2.c1
  and   t1.c2 = 1
  7  ;
 
Explained.
 
SCOTT@ora10g>select * from table(dbms_xplan.display);
 
----------------------------------------------------------------
| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)|
----------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |     1 |    52 |     7  (15)|
|*  1 |  HASH JOIN         |      |     1 |    52 |     7  (15)|
|*  2 |   TABLE ACCESS FULL| T1   |     1 |    26 |     3   (0)|
|   3 |   TABLE ACCESS FULL| T2   |  1000 | 26000 |     3   (0)|
----------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   1 - access("T1"."C1"="T2"."C1")
   2 - filter("T1"."C2"=1)


왜 Nested Loops Join 과 Hash Join 에서 Access Predicate 가 표현된느 방식의 차이가 발생하는가?
두 Join 의 동작 방식의 차이에서 비롯된다.
Nested Loops Join 은 선행 Table 에서 Key 값을 하나씩 읽으면서 후행 Table 에서 Key 값에 해당하는 값을 읽어 들이는 방식이다.
따라서 실제 Join 은 후행 Table 에 대한 Access 에서 발생한다.
따라서 후행 Table 을 읽는 단계가 Access Predicate 가 된다.

반면에 Hash Join 은 선행 Table 을 먼저 Build 한 후, 후행 Table 과 한번에 Join 하는 방식이다.
따라서 실제 Join 이 발생하는 Hash Join 단계가 Access Predicate 로 표현된다.

DBMS_XPLAN.DISPLAY Function 이 제공하는 Parameter 들을 잘 이용하면 더 많은 종류의 정보를 추출할 수 있다.
예를 들어 다음 2개의 문장은 동일한 결과를 Return 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
SCOTT@ora10g>explain plan for
select *
  2    3  from t1
  where c1 = 1 and c2 = 'dummy'
  5  ;
 
Explained.
 
SCOTT@ora10g>select * from table(dbms_xplan.display);
 
---------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)|
---------------------------------------------------------------
|   0 | SELECT STATEMENT  |      |     1 |    26 |     3   (0)|
|*  1 |  TABLE ACCESS FULL| T1   |     1 |    26 |     3   (0)|
---------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   1 - filter("C1"=1 AND "C2"=TO_NUMBER('dummy'))
 
SCOTT@ora10g>explain plan for
select *
  2    3  from t1
  where c1 = 1 and c2 = 'dummy'
  5  ;
 
Explained.
 
SCOTT@ora10g>select * from table(dbms_xplan.display('plan_table', null, 'typical',null));
 
---------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)|
---------------------------------------------------------------
|   0 | SELECT STATEMENT  |      |     1 |    26 |     3   (0)|
|*  1 |  TABLE ACCESS FULL| T1   |     1 |    26 |     3   (0)|
---------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   1 - filter("C1"=1 AND "C2"=TO_NUMBER('dummy'))


EXPLAIN PLAN 수행 시 Statement ID 를 부여한 경우에는 다음과 같이 ID 값을 지정해 주어야 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
SCOTT@ora10g>explain plan
set statement_id = 'test' for
  2    3  select *
  from t1
  where c1 = 1 and c2 = 'dummy'
  6  ;
 
Explained.
 
SCOTT@ora10g>select * from table(dbms_xplan.display('plan_table', 'test', 'typical',null));
 
---------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)|
---------------------------------------------------------------
|   0 | SELECT STATEMENT  |      |     1 |    26 |     3   (0)|
|*  1 |  TABLE ACCESS FULL| T1   |     1 |    26 |     3   (0)|
---------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   1 - filter("C1"=1 AND "C2"=TO_NUMBER('dummy'))




# 다양한 출력 Format
DBMS_XPLAN.DISPLAY Function 은 Basic, Typical, Serial, All 네 가지의 출력 Format 을 제공하며 기본값은 Typical 이다.
더불어 Outline, Advanced 라는 문서화되지 않은 Format 도 제공한다.
각 Format 의 출력 결과와 의미는 다음과 같다.

Basic Format 은 실행 계획의 단계별 Operation 과 Object 이름만을 보여주는 말 그대로 매우 기본적인 Format 이다.
실제로 사용할 경우는 없을 것이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
SCOTT@ora10g>explain plan for
select *
  2    3  from t1
  where c1 = 1 and c2 = 'dummy'
  5  ;
 
Explained.
 
SCOTT@ora10g>select * from table(dbms_xplan.display('plan_table',null,'basic'));
 
----------------------------------
| Id  | Operation         | Name |
----------------------------------
|   0 | SELECT STATEMENT  |      |
|   1 |  TABLE ACCESS FULL| T1   |
----------------------------------



Typical Format 이 가장 일반적인 용도로 고안되었다.
단계별 Operation 과 Object 명, 예측 Row 수와 Result Set 의 크기(Bytes), Cost 와 예측 실행 시간등의 정보를 보여준다.
무엇보다 Predicate 정보를 보여준다는 사실이 중요하다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
SCOTT@ora10g>explain plan for
select *
  2    3  from t1
  where c1 = 1 and c2 = 'dummy'
  5  ;
 
Explained.
 
SCOTT@ora10g>select * from table(dbms_xplan.display('plan_table',null,'typical'));
 
--------------------------------------------------------------------------
| Id  | Operation                   | Name  | Rows  | Bytes | Cost (%CPU)|
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |       |     1 |    26 |     5   (0)|
|*  1 |  TABLE ACCESS BY INDEX ROWID| T1    |     1 |    26 |     5   (0)|
|*  2 |   INDEX RANGE SCAN          | T1_N1 |     1 |       |     1   (0)|
--------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   1 - filter("C2"='dummy')
   2 - access("C1"=1)


Predicate 정보가 왜 그렇게 중요한가? 
실행 계획이 복잡해지면 Where 절의 특정 조건이 실행 계획의 각 단계에서 정확하게 어떻게 사용되었는지가 매우 중요해진다.
Query Transformation 이라는 특별한 과정 때문에 Predicate 의 변형이 발생할 때는 이 정보가 특히 중요해진다.


All Format 은 실행 계획을 분석하는데 있어서 없어서는 안될 중요한 두 가지 정보를 추가적으로 제공한다.

첫째, Query Block 정보를 제공한다.
Oracle 은 우리가 수행 요청한 SQL 문장을 Query Block 이라는 단위로 나눈다.
Query Block 은 Transformation 및 Optimization 의 기본 단위가 된다.
아래 예제에서는 SWL$1 이라는 암호화 같은 Query Block 명이 사용된 것을 확인할 수 있다.
Query Block 명은 Inline View 와 Subquery 가 많이 사용되는 복잡한 Query 를 해석할 때 특히 유용하다.

둘째, Column Projection 정보를 제공한다.
Column Projection 이란 실행 계획의 특정 단계에서 어떤 Column 을 추출하는가를 의미한다.
아래 예제에서는 2번 단게에서는 Index t1_n1 으로부터 ROWID 와 Column c1 을 추출하며, 1번 단계에서는 Column c1, c2 를 추출한다는 것을 알 수 있다.
Column Projection 정보 또한 특별한 유형의 Query Transformation 을 Troubleshooting 할 때 유용한 정보가 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25