지난 글에서 Oracle SQL과 ANSI SQL의 실행 계획 변경 사례를 소개했습니다. 이 때 사례로 사용한 쿼리는 CTAS(Create Table As Select) 유형입니다.
CTAS는 SELECT 쿼리 실행 결과를 그대로 테이블로 생성해주는 DDL문입니다. 일반적으로 SELECT 문 성능에 이상 없으면 CTAS로 변형하더라도 데이터 쓰기(Write) 부하만 추가되고 다른 성능은 큰 차이가 없습니다. SELECT 절에 해당하는 부분의 실행 계획이 동일하기 때문입니다.
하지만 이 글의 사례처럼 SELECT 문과 CTAS 문의 실행 계획이 다른 경우가 발생하기도 합니다.
준비
먼저 테스트 테이블을 준비합니다. 지난 글에서 사용한 테이블과 같습니다.
CREATE TABLE T1
AS
SELECT 1 C1, 'A' C2 FROM DUAL UNION ALL
SELECT 2 C1, 'B' C2 FROM DUAL UNION ALL
SELECT 3 C1, 'C' C2 FROM DUAL;
CREATE TABLE T2
AS
SELECT 1 C1, 'A' C2 FROM DUAL UNION ALL
SELECT 2 C1, 'B' C2 FROM DUAL;
쿼리 1 – CTAS
CTAS 쿼리는 이전 글의 결과와 마찬가지로 소트머지 조인과 Later View 쿼리 변환이 발생합니다.
CREATE TABLE TEST1
AS
SELECT /*+ LEADING(A) USE_HASH(B) */
A.C1 AS X1
, A.C2 AS X2
, B.C1 AS X3
, B.C2 AS X4
FROM T1 A LEFT OUTER JOIN T2 B
ON A.C1 = B.C1
AND A.C2 IN ( 'A', 'C' );
----------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------------
| 0 | CREATE TABLE STATEMENT | | 3 | 63 | 13 (0)| 00:00:01 |
| 1 | LOAD AS SELECT | TEST1 | | | | |
| 2 | OPTIMIZER STATISTICS GATHERING | | 3 | 63 | 12 (0)| 00:00:01 |
| 3 | MERGE JOIN OUTER | | 3 | 63 | 12 (0)| 00:00:01 |
| 4 | TABLE ACCESS FULL | T1 | 3 | 15 | 3 (0)| 00:00:01 |
| 5 | BUFFER SORT | | 1 | 16 | 9 (0)| 00:00:01 |
| 6 | VIEW | VW_LAT_DF9E236D | 1 | 16 | 3 (0)| 00:00:01 |
|* 7 | FILTER | | | | | |
|* 8 | TABLE ACCESS FULL | T2 | 1 | 5 | 3 (0)| 00:00:01 |
----------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
7 - filter("A"."C2"='A' OR "A"."C2"='C')
8 - filter("A"."C1"="B"."C1")
Hint Report (identified by operation id / Query Block Name / Object Alias):
Total hints for statement: 1 (U - Unused (1))
---------------------------------------------------------------------------
8 - SEL$1 / B@SEL$1
U - USE_HASH(B)
쿼리 2 – SELECT
CTAS 문의 SELECT 부분만 따로 실행했을 때는 옵티마이저 힌트가 적용되고 해시 조인으로 실행되는 것을 볼 수 있습니다.
SELECT /*+ LEADING(A) USE_HASH(B) */
A.C1 AS X1
, A.C2 AS X2
, B.C1 AS X3
, B.C2 AS X4
FROM T1 A LEFT OUTER JOIN T2 B
ON A.C1 = B.C1
AND A.C2 IN ( 'A', 'C' );
---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 4 | 40 | 6 (0)| 00:00:01 |
|* 1 | HASH JOIN OUTER | | 4 | 40 | 6 (0)| 00:00:01 |
| 2 | TABLE ACCESS FULL| T1 | 3 | 15 | 3 (0)| 00:00:01 |
| 3 | TABLE ACCESS FULL| T2 | 2 | 10 | 3 (0)| 00:00:01 |
---------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - access("A"."C1"="B"."C1"(+))
filter("A"."C2"=CASE WHEN ("B"."C1"(+) IS NOT NULL) THEN 'A'
ELSE 'A' END OR "A"."C2"=CASE WHEN ("B"."C1"(+) IS NOT NULL) THEN 'C'
ELSE 'C' END )
쿼리 3 – INSERT … SELECT
이번에는 CTAS와 유사한 INSERT … SELECT 문의 실행 계획을 보겠습니다. 테스트 결과 SELECT 부분은 CTAS 문과 동일한 실행 계획이 나타납니다.
INSERT INTO TEST
SELECT /*+ LEADING(A) USE_HASH(B) */
A.C1 AS X1
, A.C2 AS X2
, B.C1 AS X3
, B.C2 AS X4
FROM T1 A LEFT OUTER JOIN T2 B
ON A.C1 = B.C1
AND A.C2 IN ( 'A', 'C' );
--------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------------------
| 0 | INSERT STATEMENT | | 3 | 63 | 12 (0)| 00:00:01 |
| 1 | LOAD TABLE CONVENTIONAL | TEST | | | | |
| 2 | MERGE JOIN OUTER | | 3 | 63 | 12 (0)| 00:00:01 |
| 3 | TABLE ACCESS FULL | T1 | 3 | 15 | 3 (0)| 00:00:01 |
| 4 | BUFFER SORT | | 1 | 16 | 9 (0)| 00:00:01 |
| 5 | VIEW | VW_LAT_DF9E236D | 1 | 16 | 3 (0)| 00:00:01 |
|* 6 | FILTER | | | | | |
|* 7 | TABLE ACCESS FULL | T2 | 1 | 5 | 3 (0)| 00:00:01 |
--------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
6 - filter("A"."C2"='A' OR "A"."C2"='C')
7 - filter("A"."C1"="B"."C1")
Hint Report (identified by operation id / Query Block Name / Object Alias):
Total hints for statement: 1 (U - Unused (1))
---------------------------------------------------------------------------
7 - SEL$1 / B@SEL$1
U - USE_HASH(B)
결론
CTAS(Create Table As Select) 문의 SELECT 부분 실행 계획은 SELECT 문을 단독으로 실행할 때의 실행 계획과 다를 수 있으므로 주의해야 합니다. INSERT … SELECT 문 또한 마찬가지 입니다.
실행 계획을 확인할 때는 부분 쿼리의 실행 계획이 이상 없더라도 실행하려는 전체 쿼리 관점에서 최종 실행 계획을 확인하는 것이 필요합니다.