DROP TABLE SUBQUERY_T1 PURGE;
DROP TABLE SUBQUERY_T2 PURGE;
DROP TABLE SUBQUERY_T3 PURGE;
<SUBQUERY_T1>
* 생성요건
- 테이블건수 1,500,000
- 컬럼 C4의 값의 종류: 250,000
- 컬럼 C5의 값의 종류 : 26
- 컬럼 C6SMS 100,000 부터 시작하여, 순차적으로 증가하며 값의 종류 : 250,000
* 테이블 생성
CREATE TABLE SUBQUERY_T1
AS
SELECT LEVEL AS C4, CHR(65+MOD(LEVEL, 26)) AS C5, LEVEL+99999 AS C6
FROM DUAL
CONNECT BY LEVEL <=250000;
- 250,000개의 데이터를 생성 한 후 동일한 테이블 값을 6번 반복하여 복사
FOR I IN 1..6 LOOP
INSERT INTO SUBQUERY_T1 SELECT * FROM SUBQUERY_T1;
COMMIT;
END LOOP;
END;
/
- 각 컬럼에 인덱스 생성 및 통계정보를 수집
EXEC DBMS_STATS.GATHER_TABLE_STATS(OWNNAME=>'SCOTT', TABNAME=>'SUBQUERY_T1', CASCADE=>TRUE, ESTIMATE_PERCENT=>100);
CREATE INDEX SUBQUERY_T1_IDX1_01 ON SUBQUERY_T1(C4, C5);
CREATE INDEX SUBQUERY_T1_IDX1_02 ON SUBQUERY_T1(C5);
<SUBQUERY_T2>
* 생성요건
- 테이블건수 500,000
- 컬럼 C1 값 종류 : 500,000
- 컬럼 C2 값 종류 : 26
- 컬럼 C3 값 종류 : 500,000(100,000부터 증가)
- 컬럼 C4 값 종류 : 26
* 테이블 생성
CREATE TABLE SUBQUERY_T2
AS
SELECT LEVEL AS C1,
CHR(65+MOD(LEVEL, 26)) AS C2,
LEVEL+99999 AS C3,
CHR(65+MOD(LEVEL,26)) AS C4
FROM DUAL
CONNECT BY LEVEL <= 500000;
* 각 컬럼 인덱스 생성 및 통계정보 생성
EXEC DBMS_STATS.GATHER_TABLE_STATS(OWNNAME=> 'SCOTT', TABNAME=>'SUBQUERY_T2', CASCADE=>TRUE, ESTIMATE_PERCENT=>100);
CREATE INDEX SUBQUERY_T2_IDX_01 ON SUBQUERY_T2(C2, C1);
ALTER TABLE SUBQUERY_T2 ADD CONSTRAINT PK_SUBQUERY_2 PRIMARY KEY(C1);
<SUBQUERY_T3>
*생성 요건
- 테이블 데이터 건수 : 500,000
- 컬럼 C1 값 : 500,000
- 컬럼 C2 값 : 26
- 컬럼 C3 값 : 500,000 (100,000부터 순차적으로 증가)
*테이블 생성
CREATE TABLE SUBQUERY_T3
AS
SELECT LEVEL AS C1,
CHR(65+MOD(LEVEL, 26)) AS C2,
LEVEL+99999 AS C3
FROM DUAL
CONNECT BY LEVEL <= 500000;
* 각 컬럼 인덱스 생성 및 통계정보 생성
EXEC DBMS_STATS.GATHER_TABLE_STATS(OWNNAME=> 'SCOTT', TABNAME=>'SUBQUERY_T3', CASCADE=>TRUE, ESTIMATE_PERCENT=>100);
CREATE INDEX SUBQUERY_T3_IDX_01 ON SUBQUERY_T3(C1, C2);
ALTER TABLE SUBQUERY_T3 ADD CONSTRAINT PK_SUBQUERY_3 PRIMARY KEY(C1);
var b1 number
var b2 number
exec :b1 := 20000
exec :b2 := 400000
SELECT c1, c2, c3
FROM SUBQUERY_T2 t2
WHERE c1 >= :b1
AND c1 <= :b2
AND EXISTS (SELECT /*+ NO_UNNEST */ 'X'
FROM SUBQUERY_T1 t1
WHERE t1.c4 = t2.c1);
Call Count CPU Time Elapsed Time Disk Query Current Rows
------- ------ -------- ------------ ---------- ---------- ---------- ----------
Parse 1 0.000 0.000 0 0 0 0
Execute 1 0.000 0.000 0 0 0 0
Fetch 2302 1.326 1.378 0 789240 0 230001
------- ------ -------- ------------ ---------- ---------- ---------- ----------
Total 2304 1.326 1.378 0 789240 0 230001
Rows Row Source Operation
------- -----------------------------------------------------------------------
230001 FILTER (cr=789240 pr=0 pw=0 time=1019504 us)
380001 FILTER (cr=3611 pr=0 pw=0 time=305638 us)
380001 TABLE ACCESS FULL SUBQUERY_T2 (cr=3611 pr=0 pw=0 time=205235 us cost=373 size=4560036 card=380003)
230001 INDEX RANGE SCAN SUBQUERY_T1_IDX1_01 (cr=785629 pr=0 pw=0 time=0 us cost=3 size=10 card=2)
var b1 number
var b2 number
exec :b1 := 20000
exec :b2 := 20004
SELECT C1,
C2,
C3
FROM SUBQUERY_T2 T2
WHERE C1 >= :B1
AND C1<=:B2
AND EXISTS(SELECT /*+ NO_UNNEST */'X'
FROM SUBQUERY_T1 T1
WHERE T1.C4 = T2.C1)
Call Count CPU Time Elapsed Time Disk Query Current Rows
------- ------ -------- ------------ ---------- ---------- ---------- ----------
Parse 1 0.000 0.000 0 0 0 0
Execute 1 0.000 0.000 0 0 0 0
Fetch 2 0.000 0.000 0 21 0 5
------- ------ -------- ------------ ---------- ---------- ---------- ----------
Total 4 0.000 0.000 0 21 0 5
Rows Row Source Operation
------- -----------------------------------------------------------------------
5 FILTER (cr=21 pr=0 pw=0 time=0 us)
5 TABLE ACCESS BY INDEX ROWID SUBQUERY_T2 (cr=21 pr=0 pw=0 time=0 us cost=13 size=756 card=63)
5 INDEX RANGE SCAN PK_SUBQUERY_2 (cr=19 pr=0 pw=0 time=224 us cost=7 size=0 card=2250)
5 INDEX RANGE SCAN SUBQUERY_T1_IDX1_01 (cr=15 pr=0 pw=0 time=0 us cost=3 size=10 card=2)
var b1 number
var b2 number
exec :b1 := 20000
exec :b2 := 400000
SELECT C1,
C2,
C3
FROM SUBQUERY_T2 T2
WHERE C1 >= :B1
AND C1<=:B2
AND EXISTS(SELECT /*+ NO_UNNEST */'X'
FROM SUBQUERY_T1 T1
WHERE T1.C5 = T2.C2)
Call Count CPU Time Elapsed Time Disk Query Current Rows
------- ------ -------- ------------ ---------- ---------- ---------- ----------
Parse 1 0.000 0.001 0 0 0 0
Execute 1 0.000 0.005 0 0 0 0
Fetch 3802 0.484 0.974 60 19809 0 380001
------- ------ -------- ------------ ---------- ---------- ---------- ----------
Total 3804 0.484 0.979 60 19809 0 380001
Misses in library cashe during parse : 1
Misses in library cashe during execute : 1
Optimizer Goal : ALL_ROWS
Parsing user : SCOTT (ID=84)
Rows Row Source Operation
------- -----------------------------------------------------------------------
380001 FILTER (cr=19809 pr=60 pw=0 time=457459 us)
380001 FILTER (cr=5107 pr=0 pw=0 time=292967 us)
380001 TABLE ACCESS FULL SUBQUERY_T2 (cr=5107 pr=0 pw=0 time=185774 us cost=377 size=4560036 card=380003)
14640 INDEX RANGE SCAN SUBQUERY_T1_IDX1_02 (cr=14702 pr=60 pw=0 time=0 us cost=3 size=4 card=2)
기본적으로 SEMI/ANTI JOIN은 수행순서가 변경되지 않아 Main Query Block의 테이블이 먼저 수행.
Oracle 10부터 Hash Join의 경우에 한해 Driving Table의 순서 변경이 가능
var b1 number
var b2 number
exec :b1 := 249990
exec :b2 := 250210
SELECT c1, c2, c3
FROM SUBQUERY_T2 t2
WHERE c1 >= :b1
AND c1 <= :b2
AND EXISTS (SELECT /*+ NO_UNNEST */ 'X'
FROM SUBQUERY_T1 t1
WHERE T1.C6 = T2.C3
AND T1.C6 >= :b1)
Call Count CPU Time Elapsed Time Disk Query Current Rows
------- ------ -------- ------------ ---------- ---------- ---------- ----------
Parse 1 0.000 0.000 0 0 0 0
Execute 1 0.000 0.000 0 0 0 0
Fetch 2 311.628 932.413 7885118 9223016 0 11
------- ------ -------- ------------ ---------- ---------- ---------- ----------
Total 4 311.628 932.413 7885118 9223016 0 11
Rows Row Source Operation
------- -----------------------------------------------------------------------
11 FILTER (cr=9223016 pr=7885118 pw=0 time=0 us)
221 FILTER (cr=1317 pr=0 pw=0 time=1980 us)
221 TABLE ACCESS FULL SUBQUERY_T2 (cr=1317 pr=0 pw=0 time=1100 us cost=372 size=2702664 card=225222)
11 FILTER (cr=9221699 pr=7885118 pw=0 time=0 us)
11 TABLE ACCESS FULL SUBQUERY_T1 (cr=9221699 pr=7885118 pw=0 time=0 us cost=164 size=10 card=2)
-- c6에 인덱스를 생성 한 후 결과
SELECT c1, c2, c3
FROM SUBQUERY_T2 t2
WHERE c1 >= :b1
AND c1 <= :b2
AND EXISTS (SELECT /*+ NO_UNNEST */ 'X'
FROM SUBQUERY_T1 t1
WHERE T1.C6 = T2.C3
AND T1.C6 >= :b1)
Call Count CPU Time Elapsed Time Disk Query Current Rows
------- ------ -------- ------------ ---------- ---------- ---------- ----------
Parse 1 0.000 0.000 0 0 0 0
Execute 1 0.000 0.003 0 0 0 0
Fetch 2 0.000 0.065 8 249 0 11
------- ------ -------- ------------ ---------- ---------- ---------- ----------
Total 4 0.000 0.068 8 249 0 11
Misses in library cashe during parse : 0
Misses in library cashe during execute : 1
Optimizer Goal : ALL_ROWS
Parsing user : SCOTT (ID=84)
Rows Row Source Operation
------- -----------------------------------------------------------------------
11 FILTER (cr=249 pr=8 pw=0 time=0 us)
221 FILTER (cr=7 pr=0 pw=0 time=4400 us)
221 TABLE ACCESS BY INDEX ROWID SUBQUERY_T2 (cr=7 pr=0 pw=0 time=220 us cost=4 size=2664 card=222)
221 INDEX RANGE SCAN PK_SUBQUERY_2 (cr=4 pr=0 pw=0 time=2970 us cost=3 size=0 card=222)
11 FILTER (cr=242 pr=8 pw=0 time=0 us)
11 INDEX RANGE SCAN SUBQUERY_T1_IDX_03 (cr=242 pr=8 pw=0 time=0 us cost=3 size=5 card=1)
SELECT c1, c2, c3
FROM SUBQUERY_T2 t2
WHERE c1 >= :b1
AND c1 <= :b2
AND EXISTS (SELECT /*+ UNNEST HASH_SJ */ 'X'
FROM SUBQUERY_T1 t1
WHERE T1.C6 = T2.C3
AND T1.C6 >= :b1)
Call Count CPU Time Elapsed Time Disk Query Current Rows
------- ------ -------- ------------ ---------- ---------- ---------- ----------
Parse 1 0.000 0.000 0 0 0 0
Execute 1 0.000 0.000 0 0 0 0
Fetch 2 2.761 9.234 37509 43888 0 11
------- ------ -------- ------------ ---------- ---------- ---------- ----------
Total 4 2.761 9.235 37509 43888 0 11
Rows Row Source Operation
------- -----------------------------------------------------------------------
11 FILTER (cr=43888 pr=37509 pw=0 time=0 us)
11 HASH JOIN SEMI (cr=43888 pr=37509 pw=0 time=0 us cost=10404 size=748 card=44)
221 TABLE ACCESS BY INDEX ROWID SUBQUERY_T2 (cr=5 pr=0 pw=0 time=880 us cost=4 size=1860 card=155)
221 INDEX RANGE SCAN PK_SUBQUERY_2 (cr=3 pr=0 pw=0 time=440 us cost=3 size=0 card=222)
6400640 TABLE ACCESS FULL SUBQUERY_T1 (cr=43883 pr=37509 pw=0 time=5948287 us cost=10368 size=32003330 card=6400666)
var b1 number
var b2 number
var b3 number
var b4 number
exec :b1 := 1
exec :b2 := 450210
exec :b3 := 100000
exec :b4 := 100004
SELECT C4,
C5,
C6
FROM SUBQUERY_T1 T1
WHERE C6 >= :B1
AND C6 <= :B2
AND EXISTS (SELECT /*+ NO_UNNEST */'X'
FROM SUBQUERY_T2 T2
WHERE T2.C1 = T1.C4
AND T2.C3 >= :B3
AND T2.C3 <= :B4)
Call Count CPU Time Elapsed Time Disk Query Current Rows
------- ------ -------- ------------ ---------- ---------- ---------- ----------
Parse 1 0.000 0.001 0 0 0 0
Execute 1 0.000 0.009 0 31 0 0
Fetch 5 63.929 63.982 37509 33288865 0 320
------- ------ -------- ------------ ---------- ---------- ---------- ----------
Total 7 63.929 63.991 37509 33288896 0 320
Misses in library cashe during parse : 1
Misses in library cashe during execute : 1
Optimizer Goal : ALL_ROWS
Parsing user : SCOTT (ID=84)
Rows Row Source Operation
------- -----------------------------------------------------------------------
320 FILTER (cr=33288865 pr=37509 pw=0 time=159 us)
16000000 FILTER (cr=43886 pr=37509 pw=0 time=16265983 us)
16000000 TABLE ACCESS FULL SUBQUERY_T1 (cr=43886 pr=37509 pw=0 time=12611300 us cost=10424 size=192000000 card=16000000)
5 FILTER (cr=33244979 pr=0 pw=0 time=0 us)
5 TABLE ACCESS BY INDEX ROWID SUBQUERY_T2 (cr=33244979 pr=0 pw=0 time=0 us cost=3 size=10 card=1)
15935488 INDEX UNIQUE SCAN PK_SUBQUERY_2 (cr=17309491 pr=0 pw=0 time=0 us cost=2 size=0 card=1)
SELECT C4,
C5,
C6
FROM SUBQUERY_T1 T1
WHERE C6 >= :B1
AND C6 <= :B2
AND EXISTS (SELECT /*+ UNNEST HASH_SJ */'X'
FROM SUBQUERY_T2 T2
WHERE T2.C1 = T1.C4
AND T2.C3 >= :B3
AND T2.C3 <= :B4)
Call Count CPU Time Elapsed Time Disk Query Current Rows
------- ------ -------- ------------ ---------- ---------- ---------- ----------
Parse 1 0.000 0.000 0 0 0 0
Execute 1 0.000 0.000 0 0 0 0
Fetch 5 4.103 5.269 37509 45202 0 320
------- ------ -------- ------------ ---------- ---------- ---------- ----------
Total 7 4.103 5.269 37509 45202 0 320
Misses in library cashe during parse : 0
Optimizer Goal : ALL_ROWS
Parsing user : SCOTT (ID=84)
Rows Row Source Operation
------- -----------------------------------------------------------------------
320 FILTER (cr=45202 pr=37509 pw=0 time=319 us)
320 HASH JOIN RIGHT SEMI (cr=45202 pr=37509 pw=0 time=319 us cost=10861 size=8448 card=384)
5 TABLE ACCESS FULL SUBQUERY_T2 (cr=1316 pr=0 pw=0 time=8 us cost=374 size=50 card=5)
16000000 TABLE ACCESS FULL SUBQUERY_T1 (cr=43886 pr=37509 pw=0 time=8017471 us cost=10408 size=192000000 card=16000000)
SELECT /*+ QB_NAME(MAIN) LEADING(T2@SUB) USE_NL(T1@MAIN) */
C4,
C5,
C6
FROM SUBQUERY_T1 T1
WHERE C6 >= :B1
AND C6 <= :B2
AND EXISTS (SELECT /*+ UNNEST QB_NAME(SUB) */'X'
FROM SUBQUERY_T2 T2
WHERE T2.C1 = T1.C4
AND T2.C3 >= :B3
AND T2.C3 <= :B4)
Call Count CPU Time Elapsed Time Disk Query Current Rows
------- ------ -------- ------------ ---------- ---------- ---------- ----------
Parse 1 0.000 0.000 0 0 0 0
Execute 1 0.000 0.000 0 0 0 0
Fetch 5 0.062 0.052 0 1654 0 320
------- ------ -------- ------------ ---------- ---------- ---------- ----------
Total 7 0.062 0.053 0 1654 0 320
Misses in library cashe during parse : 0
Optimizer Goal : ALL_ROWS
Parsing user : SCOTT (ID=84)
Rows Row Source Operation
------- -----------------------------------------------------------------------
320 FILTER (cr=1654 pr=0 pw=0 time=3509 us)
320 NESTED LOOPS (cr=1654 pr=0 pw=0 time=3349 us)
320 NESTED LOOPS (cr=1334 pr=0 pw=0 time=2871 us cost=477 size=7040 card=320)
5 SORT UNIQUE (cr=1316 pr=0 pw=0 time=8 us cost=374 size=50 card=5)
5 TABLE ACCESS FULL SUBQUERY_T2 (cr=1316 pr=0 pw=0 time=4 us cost=374 size=50 card=5)
320 INDEX RANGE SCAN SUBQUERY_T1_IDX1_01 (cr=18 pr=0 pw=0 time=567 us cost=2 size=0 card=64)
320 TABLE ACCESS BY INDEX ROWID SUBQUERY_T1 (cr=320 pr=0 pw=0 time=0 us cost=66 size=768 card=64)
HINT명 | 설명 |
---|---|
NO_UNNEST | 서브쿼리를 FILTER동작방식으로 처리하고 싶을 경우, 서브쿼리에 NO_UNNEST 힌트를 사용 |
UNNEST | FILTER 동작방식을 선택하지 않고 조인 동작방식으로 처리하고자 할 때. 서브쿼리에 UNNEST 힌트 사용 |
NL_SJ | EXISTS나 IN조건 사용시 서브쿼리에 UNNEST와 함께 NL_SJ힌트를 사용하면, NESTED LOOPS JOIN SEMI로 처리되도록 유도 |
HASH_SJ | EXISTS나 IN조건을 사용한 경우 서브쿼리에 UNNEST와 함께 HASH_SJ 힌트를 부여하면 HASH JOIN SEMI로 처리하도록 제어 |
NL_AJ | NOT EXISTS나 NOT IN 조건을 사용한 경우 서브쿼리에 UNNEST와 함께 NL_AJ 힌트를 사용하면, NESTED LOOPS JOIN ANTI로 처리하도록 제어 |
HASH_AJ | NOT EXISTS나 NOT IN 조건을 사용한 경우 서브쿼리에 UNNEST와 함께 HASH_AJ 힌트를 사용하면 HASH JOIN ANTI로 처리하도록 제어 |
ORDERED | FROM절에 나열된 순서대로 수행하도록 조인 순서를 정하는 힌트. 서브쿼리가 존재한다면 서브쿼리가 가장 먼저 수행 |
QB_NAME | QUERY BLOCK의 이름을 지정 |
SWAP_JOIN_INPUTS | HASH JOIN시 조인 순서를 변경 가능. 명시된 테이블이 BUILD 테이블이 됨 |
NO_SWAP_JOIN_INPUTS | HASH JOIN시 조인 순서가 바뀌는 경우, 이를 강제적으로 변경되지 못하도록 제어 |
PUSH_SUBQ | 서브쿼리가 먼저 수행하도록 제어. FILTER로 수행됨 |
NO_UNNEST 힌트를 부여하면, Filter 동작 방식으로 수행하도록 제어할 수 있다.
SELECT C4,
C5,
C6
FROM SUBQUERY_T1 T1
WHERE C6 >= :B1
AND C6 <= :B2
AND EXISTS(SELECT /*+ NO_UNNEST*/ 'X'
FROM SUBQUERY_T2 T2
WHERE T2.C1 = T1.C4
AND T2.C3 >= :B3
AND T2.C3 <= :B4);
-----------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 12 | 97718 (1)| 00:19:33 |
|* 1 | FILTER | | | | | |
|* 2 | FILTER | | | | | |
|* 3 | TABLE ACCESS FULL | SUBQUERY_T1 | 40000 | 468K| 10453 (3)| 00:02:06 |
|* 4 | FILTER | | | | | |
|* 5 | TABLE ACCESS BY INDEX ROWID| SUBQUERY_T2 | 1 | 10 | 3 (0)| 00:00:01 |
|* 6 | INDEX UNIQUE SCAN | PK_SUBQUERY_2 | 1 | | 2 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------
NO_UNNEST 힌트를 부여하면, Filter 동작 방식으로 수행하도록 제어할 수 있다.
SELECT C4,
C5,
C6
FROM SUBQUERY_T1 T1
WHERE C6 >= :B1
AND C6 <= :B2
AND EXISTS(SELECT /*+ UNNEST NL_SJ*/ 'X'
FROM SUBQUERY_T2 T2
WHERE T2.C1 = T1.C4
AND T2.C3 >= :B3
AND T2.C3 <= :B4);
-----------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1352 | 29744 | 90486 (1)| 00:18:06 |
|* 1 | FILTER | | | | | |
| 2 | NESTED LOOPS SEMI | | 1352 | 29744 | 90486 (1)| 00:18:06 |
|* 3 | TABLE ACCESS FULL | SUBQUERY_T1 | 40000 | 468K| 10453 (3)| 00:02:06 |
|* 4 | TABLE ACCESS BY INDEX ROWID| SUBQUERY_T2 | 42 | 420 | 2 (0)| 00:00:01 |
|* 5 | INDEX UNIQUE SCAN | PK_SUBQUERY_2 | 1 | | 1 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------
UNNEST와 HASH_SJ, SWAP_JOIN_INPUTS힌트를 사용하면 서브쿼리부터 수행하도록 실행계획 제어 가능
SELECT
C4,
C5,
C6
FROM SUBQUERY_T1 T1
WHERE C6 >= :B1
AND C6 <= :B2
AND EXISTS(SELECT /*+ UNNEST HASH_SJ SWAP_JOIN_INPUTS(T2)*/ 'X'
FROM SUBQUERY_T2 T2
WHERE T2.C1 = T1.C4
AND T2.C3 >= :B3
AND T2.C3 <= :B4);
----------------------------------------------------------------------------------
d | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------
0 | SELECT STATEMENT | | 1352 | 29744 | 10828 (3)| 00:02:10 |
1 | FILTER | | | | | |
2 | HASH JOIN RIGHT SEMI| | 1352 | 29744 | 10828 (3)| 00:02:10 |
3 | TABLE ACCESS FULL | SUBQUERY_T2 | 1250 | 12500 | 374 (3)| 00:00:05 |
4 | TABLE ACCESS FULL | SUBQUERY_T1 | 40000 | 468K| 10453 (3)| 00:02:06 |
----------------------------------------------------------------------------------
UNNEST와 HASH_SJ 힌트를 사용하면, HASH SEMI JOIN으로 수행하도록 제어
SEMI JOIN은 MAIN SQL쪽 테이블을 먼저 수행하는 것이 기본이나 HASH RIGHT SEMI JOIN으로 수행되면 조인 순서가 변경되므로
NO_SWAP_JOIN_INPUTS 힌트를 명시적으로 사용
SELECT C4,
C5,
C6
FROM SUBQUERY_T1 T1
WHERE C6 >= :B1
AND C6 <= :B2
AND EXISTS(SELECT /*+ UNNEST HASH_SJ NO_SWAP_JOIN_INPUTS(T2) */ 'X'
FROM SUBQUERY_T2 T2
WHERE T2.C1 = T1.C4
AND T2.C3 >= :B3
AND T2.C3 <= :B4);
-----------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1352 | 29744 | 10828 (3)| 00:02:10 |
|* 1 | FILTER | | | | | |
|* 2 | HASH JOIN SEMI | | 1352 | 29744 | 10828 (3)| 00:02:10 |
|* 3 | TABLE ACCESS FULL| SUBQUERY_T1 | 40000 | 468K| 10453 (3)| 00:02:06 |
|* 4 | TABLE ACCESS FULL| SUBQUERY_T2 | 1250 | 12500 | 374 (3)| 00:00:05 |
-----------------------------------------------------------------------------------
QB_NAME 힌트를 사용해 QUERY BLOCK명을 지정한 후, QUERY BLOCK 명을 지정한 후, 조인 순서와 조인 방법을 제어
SELECT /*+ QB_NAME(MAIN) LEADING(T2@SUB) USE_NAME(T1@MAIN) */
C4,
C5,
C6
FROM SUBQUERY_T1 T1
WHERE C6 >= :B1
AND C6 <= :B2
AND EXISTS(SELECT /*+ UNNEST QB_NAME(SUB) */ 'X'
FROM SUBQUERY_T2 T2
WHERE T2.C1 = T1.C4
AND T2.C3 >= :B3
AND T2.C3 <= :B4);
------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1351 | 29722 | 10829 (3)| 00:02:10 |
|* 1 | FILTER | | | | | |
|* 2 | HASH JOIN | | 1351 | 29722 | 10829 (3)| 00:02:10 |
| 3 | SORT UNIQUE | | 1250 | 12500 | 374 (3)| 00:00:05 |
|* 4 | TABLE ACCESS FULL| SUBQUERY_T2 | 1250 | 12500 | 374 (3)| 00:00:05 |
|* 5 | TABLE ACCESS FULL | SUBQUERY_T1 | 40000 | 468K| 10453 (3)| 00:02:06 |
------------------------------------------------------------------------------------
SELECT /*+ QB_NAME(MAIN) LEADING(T2@SUB) USE_HASH(T1@MAIN) */
C4,
C5,
C6
FROM SUBQUERY_T1 T1
WHERE C6 >= :B1
AND C6 <= :B2
AND EXISTS(SELECT /*+ UNNEST QB_NAME(SUB) */ 'X'
FROM SUBQUERY_T2 T2
WHERE T2.C1 = T1.C4
AND T2.C3 >= :B3
AND T2.C3 <= :B4);
------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1351 | 29722 | 10829 (3)| 00:02:10 |
|* 1 | FILTER | | | | | |
|* 2 | HASH JOIN | | 1351 | 29722 | 10829 (3)| 00:02:10 |
| 3 | SORT UNIQUE | | 1250 | 12500 | 374 (3)| 00:00:05 |
|* 4 | TABLE ACCESS FULL| SUBQUERY_T2 | 1250 | 12500 | 374 (3)| 00:00:05 |
|* 5 | TABLE ACCESS FULL | SUBQUERY_T1 | 40000 | 468K| 10453 (3)| 00:02:06 |
------------------------------------------------------------------------------------
NOT EXISTS의 경우 NL JOIN ANTI로 수행 제어 하기 위해서 UNNEST, NL_AJ힌트를 부여
SELECT C4,
C5,
C6
FROM SUBQUERY_T1 T1
WHERE C6 >= :B1
AND C6 <= :B2
AND EXISTS(SELECT /*+ UNNEST NL_AJ */ 'X'
FROM SUBQUERY_T2 T2
WHERE T2.C1 = T1.C4
AND T2.C3 >= :B3
AND T2.C3 <= :B4);
-------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1352 | 29744 | 10828 (3)| 00:02:10 |
|* 1 | FILTER | | | | | |
|* 2 | HASH JOIN RIGHT SEMI| | 1352 | 29744 | 10828 (3)| 00:02:10 |
|* 3 | TABLE ACCESS FULL | SUBQUERY_T2 | 1250 | 12500 | 374 (3)| 00:00:05 |
|* 4 | TABLE ACCESS FULL | SUBQUERY_T1 | 40000 | 468K| 10453 (3)| 00:02:06 |
-------------------------------------------------------------------------------------
SELECT C4,
C5,
C6
FROM SUBQUERY_T1 T1
WHERE C6 >= :B1
AND C6 <= :B2
AND EXISTS(SELECT /*+ UNNEST HASH_AJ */ 'X'
FROM SUBQUERY_T2 T2
WHERE T2.C1 = T1.C4
AND T2.C3 >= :B3
AND T2.C3 <= :B4);
-------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1352 | 29744 | 10828 (3)| 00:02:10 |
|* 1 | FILTER | | | | | |
|* 2 | HASH JOIN RIGHT SEMI| | 1352 | 29744 | 10828 (3)| 00:02:10 |
|* 3 | TABLE ACCESS FULL | SUBQUERY_T2 | 1250 | 12500 | 374 (3)| 00:00:05 |
|* 4 | TABLE ACCESS FULL | SUBQUERY_T1 | 40000 | 468K| 10453 (3)| 00:02:06 |
-------------------------------------------------------------------------------------
h9. 서브쿼리를 먼저 읽은 후, NL 으로 수행
SELECT
*
FROM EMP E
WHERE EMPNO IN (SELECT
MAX(EMPNO)
FROM EMP X
GROUP BY DEPTNO);
-- 서브쿼리를 먼저 읽은 후, NL로 수행
SELECT /*+ LEADING(X@SUB) QB_NAME(MAIN) USE_NL(E@MAIN) */
*
FROM EMP E
WHERE EMPNO IN (SELECT /*+ UNNEST QB_NAME(SUB) */
MAX(EMPNO)
FROM EMP X
GROUP BY DEPTNO);
---------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 14 | 1400 | 8 (25)| 00:00:01 |
|* 1 | HASH JOIN | | 14 | 1400 | 8 (25)| 00:00:01 |
| 2 | TABLE ACCESS FULL | EMP | 14 | 1218 | 3 (0)| 00:00:01 |
| 3 | VIEW | VW_NSO_1 | 14 | 182 | 4 (25)| 00:00:01 |
| 4 | HASH GROUP BY | | 14 | 364 | 4 (25)| 00:00:01 |
| 5 | TABLE ACCESS FULL| EMP | 14 | 364 | 3 (0)| 00:00:01 |
---------------------------------------------------------------------------------
SELECT /*+ ORDERED USE_NL(E) */
*
FROM EMP E
WHERE EMPNO IN (SELECT /*+ UNNEST */
MAX(EMPNO)
FROM EMP X
GROUP BY DEPTNO);
-----------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 14 | 1400 | 18 (6)| 00:00:01 |
| 1 | NESTED LOOPS | | | | | |
| 2 | NESTED LOOPS | | 14 | 1400 | 18 (6)| 00:00:01 |
| 3 | VIEW | VW_NSO_1 | 14 | 182 | 4 (25)| 00:00:01 |
| 4 | HASH GROUP BY | | 14 | 364 | 4 (25)| 00:00:01 |
| 5 | TABLE ACCESS FULL | EMP | 14 | 364 | 3 (0)| 00:00:01 |
|* 6 | INDEX UNIQUE SCAN | PK_EMP | 1 | | 0 (0)| 00:00:01 |
| 7 | TABLE ACCESS BY INDEX ROWID| EMP | 1 | 87 | 1 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------