• 스칼라 서브쿼리와 조인의 이해 및 활용하기.
    실무에서 흔히 접할 수 있는 스칼라 서브쿼리의 성능 문제는 크게 두 가지 유형
    첫번째. 스칼라 서브쿼리의 수행 위치에 따른 성능 문제
    두번째. 스칼라 서브쿼리와 조인의 관계에서 발생하는 성능 문제

1. 스칼라 서브쿼리는 최종 결과 만큼 수행하자.

  • SQL 설명: SCALAR_T1 테이블 전체 데이터 대상으로 C1, C2 컬럼으로 오름차순 정렬 후 10 건만 가져오는 SQL 을 작성하고자 한다.
    이때 추줄할 데이터는 SCALAR_T1의 C1, C2, C3 와 SCALAR_T2 의 C3 컬럼 값이다.
    단,SCALAR_T2 의 C3 컬럼 값은 SCALAR_T1 에서 추출한 C1 과 SCALAR_T2 C1 값이 동일한 경우에만 추출하고
    동일한 데이터가 없다면 NULL 을 주줄해야 한다.

– TEST3. 스칼라 서브쿼리의 수행 위치에 따른 성능 문제
-- 스칼라 서브쿼리 작성 1.
SELECT ROWNUM rnum, x.*
FROM (
SELECT c1,c2,c3,
(SELECT t2.C3
FROM SCALAR_T2 T2
WHERE t2.c1 = t1.c1) AS t2_c3
FROM SCALAR_T1 T1
ORDER BY c1 , c2
) x
WHERE ROWNUM <= 10 ;

*************************[Explain Plan Time: 2013/09/05 15:21:07]*************************
Execution Plan















---
0 SELECT STATEMENT Optimizer=ALL_ROWS (Cost=3K Card=10 Bytes=510)
1 0 COUNT (STOPKEY)
2 1 VIEW (Cost=3K Card=500K Bytes=24M)
3 2 SORT (ORDER BY STOPKEY) (Cost=3K Card=500K Bytes=5M)
4 3 TABLE ACCESS (FULL) OF 'SCALAR_T1' (TABLE) (Cost=282 Card=500K Bytes=5M)














---

Predicate information (identified by operation id):















---
1 - filter(ROWNUM<=10)
3 - filter(ROWNUM<=10)














---

-- 스칼라서브쿼리 작성 2.

SELECT ROWNUM rnum, x.* ,
(SELECT t2.c3
FROM SCALAR_T2 T2
WHERE t2.c1 = x.c1) AS t2_c3
FROM (
SELECT c1, c2, c3
FROM SCALAR_T1 T1
ORDER BY c1, c2
) x
WHERE ROWNUM <= 10 ;

*************************[Explain Plan Time: 2013/09/05 15:21:36]*************************
Execution Plan















---
0 SELECT STATEMENT Optimizer=ALL_ROWS (Cost=3K Card=10 Bytes=290)
1 0 TABLE ACCESS (BY INDEX ROWID) OF 'SCALAR_T2' (TABLE) (Cost=4 Card=1 Bytes=7)
2 1 INDEX (RANGE SCAN) OF 'SCALAR_T2_IDX_01' (INDEX) (Cost=3 Card=1)
3 0 COUNT (STOPKEY)
4 3 VIEW (Cost=3K Card=500K Bytes=14M)
5 4 SORT (ORDER BY STOPKEY) (Cost=3K Card=500K Bytes=5M)
6 5 TABLE ACCESS (FULL) OF 'SCALAR_T1' (TABLE) (Cost=282 Card=500K Bytes=5M)














---

Predicate information (identified by operation id):















---
2 - access("T2"."C1"=:B1)
3 - filter(ROWNUM<=10)
5 - filter(ROWNUM<=10)














---

2. 스칼라 서브쿼리와 조인의 관계로 보는 SQL

  • 비효율 스칼라 서브쿼리는 조인으로 변경하자.
    (TEST4.rownum검색으로 인한 COUNT STOPKEY를 이용한 튜닝기법 인라인뷰를 COUNT STOPKEY로 최적화 하기)

SELECT ROWNUM rnum, x.*
FROM (
SELECT c1,c2,c3,
(SELECT t2.C3
FROM SCALAR_T2 T2
WHERE t2.c1 = t1.c1) AS t2_c3
FROM SCALAR_T1 T1
ORDER BY c1 , c2
) x
WHERE ROWNUM <= 10

call count cpu elapsed disk query current rows


---
--



--

--

--

--

--
Parse 1 0.00 0.03 0 0 0 0
Execute 1 0.00 0.01 0 0 0 0
Fetch 2 1.84 1.84 0 1502292 0 10

---
--



--

--

--

--

--
total 4 1.84 1.89 0 1502292 0 10

Misses in library cache during parse: 1
Optimizer mode: ALL_ROWS
Parsing user id: 54 (SCOTT)

Rows Row Source Operation


---











---
10 COUNT STOPKEY (cr=1502292 pr=0 pw=0 time=1846759 us)
10 VIEW (cr=1502292 pr=0 pw=0 time=1846755 us)
10 SORT ORDER BY STOPKEY (cr=1502292 pr=0 pw=0 time=1846752 us)
500000 TABLE ACCESS FULL SCALAR_T1 (cr=1178 pr=0 pw=0 time=38 us)

Rows Execution Plan


---











---
0 SELECT STATEMENT MODE: ALL_ROWS
10 COUNT (STOPKEY)
10 VIEW
10 SORT (ORDER BY STOPKEY)
500000 TABLE ACCESS MODE: ANALYZED (FULL) OF 'SCALAR_T1' (TABLE)

SELECT ROWNUM rnum, x.* ,
(SELECT t2.c3
FROM SCALAR_T2 T2
WHERE t2.c1 = x.c1) AS t2_c3
FROM (
SELECT c1, c2, c3
FROM SCALAR_T1 T1
ORDER BY c1, c2
) x
WHERE ROWNUM <= 10

call count cpu elapsed disk query current rows


---
--



--

--

--

--

--
Parse 1 0.00 0.00 0 0 0 0
Execute 1 0.00 0.00 0 0 0 0
Fetch 2 0.09 0.09 0 1210 0 10

---
--



--

--

--

--

--
total 4 0.09 0.09 0 1210 0 10

Misses in library cache during parse: 1
Optimizer mode: ALL_ROWS
Parsing user id: 54 (SCOTT)

Rows Row Source Operation


---











---
10 TABLE ACCESS BY INDEX ROWID SCALAR_T2 (cr=32 pr=0 pw=0 time=93 us)
10 INDEX RANGE SCAN SCALAR_T2_IDX_01 (cr=22 pr=0 pw=0 time=62 us)(object id 53840)
10 COUNT STOPKEY (cr=1178 pr=0 pw=0 time=92552 us)
10 VIEW (cr=1178 pr=0 pw=0 time=92547 us)
10 SORT ORDER BY STOPKEY (cr=1178 pr=0 pw=0 time=92546 us)
500000 TABLE ACCESS FULL SCALAR_T1 (cr=1178 pr=0 pw=0 time=59 us)

Rows Execution Plan


---











---
0 SELECT STATEMENT MODE: ALL_ROWS
10 TABLE ACCESS MODE: ANALYZED (BY INDEX ROWID) OF 'SCALAR_T2' (TABLE)
10 INDEX MODE: ANALYZED (RANGE SCAN) OF 'SCALAR_T2_IDX_01' (INDEX)
10 COUNT (STOPKEY)
10 VIEW
10 SORT (ORDER BY STOPKEY)
500000 TABLE ACCESS MODE: ANALYZED (FULL) OF 'SCALAR_T1' (TABLE)

두 SQL 의 IO 처리량의 차이
SCALAR_T2에서 P2_C3 컬럼 값을 추출하기 위해 스칼라 서브쿼리를 사용하였다.

SQL[l] 은 SCALAR_T1 테이블의 모든 데이터 건수인 500000 건 만큼 스칼라 서브쿼리(SCALAR_T2)가
반복 수행되어 총 1501114 블록을 읽었다.
1501114 = 1502292 (전체 처리 Block) - 1178 (스칼라 서브쿼리 수행이전 처리 Block)

SQL[2]는 SCALAR_T1 테이블을 먼저 C1,C2 컬럼으로 정렬한후,ROWNUM 조건으로 10 건만 추출하여
스칼라 서브쿼리 (SCALAR_T2)를 수행했다.

그러므로 SQL[2]는 SQL[1] 에 비해 스칼라 서브쿼리의 수행횟수가 줄어, SQL[1] 에 비해
매우 적은 1/0 처리량으로 SQL 을 수행하였다.

  • 조인을 스칼라서브쿼리로 변경하자.
    (TEST5.스칼라 서브쿼리는 반드시 NL조인하는 특성이 존재로 인한 비효율을 제거하는 방법으로 해쉬 아우터 조인으로 변형하여 성능개선 해보기)

튜닝전]
SELECT T1.C1 ,T1.C2, T1.C3,
(SELECT T2.C3
FROM SCALAR_T2 T2
WHERE T2.C1 = T1.C1) AS T2_C3,
(SELECT T3.C3
FROM SCALAR_T3 T3
WHERE T3.C1 = T1.C1) AS T3_C3
FROM SCALAR_T1 T1
ORDER BY T1.C1, T1.C2 ;

call count cpu elapsed disk query current rows


---
--



--

--

--

--

--
Parse 1 0.00 0.00 0 0 0 0
Execute 1 0.00 0.00 0 0 0 0
Fetch 33335 5.46 5.51 0 3003406 0 500000

---
--



--

--

--

--

--
total 33337 5.46 5.51 0 3003406 0 500000

Misses in library cache during parse: 0
Optimizer mode: ALL_ROWS
Parsing user id: 54 (SCOTT)

Rows Row Source Operation


---











---
500000 TABLE ACCESS BY INDEX ROWID SCALAR_T2 (cr=1501114 pr=0 pw=0 time=1765145 us)
500000 INDEX RANGE SCAN SCALAR_T2_IDX_01 (cr=1001114 pr=0 pw=0 time=1063894 us)(object id 53840)
500000 TABLE ACCESS BY INDEX ROWID SCALAR_T3 (cr=1501114 pr=0 pw=0 time=1805023 us)
500000 INDEX RANGE SCAN SCALAR_T3_IDX_01 (cr=1001114 pr=0 pw=0 time=1090502 us)(object id 53842)
500000 SORT ORDER BY (cr=3003406 pr=0 pw=0 time=4138725 us)
500000 TABLE ACCESS FULL SCALAR_T1 (cr=1178 pr=0 pw=0 time=43 us)

튜닝후]
SELECT /*+ LEADING(T1) USE_HASH(T1 T2 T3) */
t1.c1, t1.c2, t1.c3,
t2.c3 AS t2_c3, t3.c3 AS t3_c3
FROM SCALAR_T1 T1, SCALAR_T2 T2, SCALAR_T3 T3
WHERE t1.c1 = t2.c1(+)
AND t1.c1 = t3.c1(+)
ORDER BY t1.c1, t1.c2;

call count cpu elapsed disk query current rows


---
--



--

--

--

--

--
Parse 1 0.01 0.00 0 0 0 0
Execute 1 0.00 0.00 0 0 0 0
Fetch 33335 2.13 2.56 775 3169 0 500000

---
--



--

--

--

--

--
total 33337 2.15 2.57 775 3169 0 500000

Misses in library cache during parse: 1
Optimizer mode: ALL_ROWS
Parsing user id: 54 (SCOTT)

Rows Row Source Operation


---











---
500000 SORT ORDER BY (cr=3169 pr=775 pw=775 time=1409577 us)
500000 HASH JOIN RIGHT OUTER (cr=3169 pr=775 pw=775 time=1393607 us)
500000 TABLE ACCESS FULL SCALAR_T3 (cr=1033 pr=0 pw=0 time=31 us)
500000 HASH JOIN OUTER (cr=2136 pr=775 pw=775 time=755405 us)
500000 TABLE ACCESS FULL SCALAR_T1 (cr=1178 pr=0 pw=0 time=34 us)
500000 TABLE ACCESS FULL SCALAR_T2 (cr=958 pr=0 pw=0 time=74 us)

튜닝전]
SELECT ROWNUM rnum, X.*
FROM (
SELECT /*+ USE_NL(T1 T2 T3) */
t1.c1, t1.c2, t1.c3,
t2.c3 AS t2_c3, t3.c3 AS t3_c3
FROM SCALAR_T1 T1, SCALAR_T2 T2, SCALAR_T3 T3
WHERE t1.c1 = t2.c1(+)
AND t1.c1 = t3.c1(+)
ORDER BY t1.c1, t1.c2
) X
WHERE ROWNUM <= 10 ;

call count cpu elapsed disk query current rows


---
--



--

--

--

--

--
Parse 1 0.00 0.00 0 0 0 0
Execute 1 0.00 0.00 0 0 0 0
Fetch 2 5.47 5.47 0 3003406 0 10

---
--



--

--

--

--

--
total 4 5.47 5.47 0 3003406 0 10

Misses in library cache during parse: 0
Optimizer mode: ALL_ROWS
Parsing user id: 54 (SCOTT)

Rows Row Source Operation


---











---
10 COUNT STOPKEY (cr=3003406 pr=0 pw=0 time=5477329 us)
10 VIEW (cr=3003406 pr=0 pw=0 time=5477326 us)
10 SORT ORDER BY STOPKEY (cr=3003406 pr=0 pw=0 time=5477324 us)
500000 NESTED LOOPS OUTER (cr=3003406 pr=0 pw=0 time=5500070 us)
500000 NESTED LOOPS OUTER (cr=1502292 pr=0 pw=0 time=3000066 us)
500000 TABLE ACCESS FULL SCALAR_T1 (cr=1178 pr=0 pw=0 time=47 us)
500000 TABLE ACCESS BY INDEX ROWID SCALAR_T2 (cr=1501114 pr=0 pw=0 time=2138604 us)
500000 INDEX RANGE SCAN SCALAR_T2_IDX_01 (cr=1001114 pr=0 pw=0 time=1121809 us)(object id 53840)
500000 TABLE ACCESS BY INDEX ROWID SCALAR_T3 (cr=1501114 pr=0 pw=0 time=2348275 us)
500000 INDEX RANGE SCAN SCALAR_T3_IDX_01 (cr=1001114 pr=0 pw=0 time=1041701 us)(object id 53842)

튜닝1. 해쉬변환]
SELECT ROWNUM rnum, X.*
FROM (
SELECT /*+ LEADING(T1) USE HASH(T1 T2 T3) */
t1.c1, t1.c2, t1.c3,
t2.c3 AS t2_c3, t3.c3 AS t3_c3
FROM SCALAR_T1 T1, SCALAR_T2 T2, SCALAR_T3 T3
WHERE t1.c1 = t2.c1( +)
AND t1.c1 = t3.c1(+)
ORDER BY t1.c1 , t1.c2
) X
WHERE ROWNUM <= 10

call count cpu elapsed disk query current rows


---
--



--

--

--

--

--
Parse 1 0.00 0.00 0 0 0 0
Execute 1 0.00 0.00 0 0 0 0
Fetch 2 0.98 1.29 777 3169 0 10

---
--



--

--

--

--

--
total 4 0.98 1.29 777 3169 0 10

Misses in library cache during parse: 1
Optimizer mode: ALL_ROWS
Parsing user id: 54 (SCOTT)

Rows Row Source Operation


---











---
10 COUNT STOPKEY (cr=3169 pr=777 pw=775 time=1296755 us)
10 VIEW (cr=3169 pr=777 pw=775 time=1296749 us)
10 SORT ORDER BY STOPKEY (cr=3169 pr=777 pw=775 time=1296748 us)
500000 HASH JOIN RIGHT OUTER (cr=3169 pr=777 pw=775 time=1202939 us)
500000 TABLE ACCESS FULL SCALAR_T3 (cr=1033 pr=1 pw=0 time=14311 us)
500000 HASH JOIN OUTER (cr=2136 pr=776 pw=775 time=519969 us)
500000 TABLE ACCESS FULL SCALAR_T1 (cr=1178 pr=0 pw=0 time=36 us)
500000 TABLE ACCESS FULL SCALAR_T2 (cr=958 pr=1 pw=0 time=2389 us)

튜닝2. 최종]
SELECT ROWNUM rnum, X.*,
(SELECT t2.C3
FROM scalar_t2 t2
WHERE t2.c1 = x.c1) AS t2_c3 ,
(SELECT t3.C3
FROM scalar_t3 t3
WHERE t3.c1 = x.c1) AS t3_c3
FROM (SELECT t1.C1, t1.c2, t1.c3
FROM scalar_t1 t1
ORDER BY t1.c1, t1.c2 ) X
WHERE ROWNUM <= 10

call count cpu elapsed disk query current rows


---
--



--

--

--

--

--
Parse 1 0.00 0.00 0 0 0 0
Execute 1 0.00 0.00 0 0 0 0
Fetch 2 0.07 0.07 0 1242 0 10

---
--



--

--

--

--

--
total 4 0.07 0.07 0 1242 0 10

Misses in library cache during parse: 1
Optimizer mode: ALL_ROWS
Parsing user id: 54 (SCOTT)

Rows Row Source Operation


---











---
10 TABLE ACCESS BY INDEX ROWID SCALAR_T2 (cr=32 pr=0 pw=0 time=62 us)
10 INDEX RANGE SCAN SCALAR_T2_IDX_01 (cr=22 pr=0 pw=0 time=35 us)(object id 53840)
10 TABLE ACCESS BY INDEX ROWID SCALAR_T3 (cr=32 pr=0 pw=0 time=36 us)
10 INDEX RANGE SCAN SCALAR_T3_IDX_01 (cr=22 pr=0 pw=0 time=23 us)(object id 53842)
10 COUNT STOPKEY (cr=1178 pr=0 pw=0 time=76565 us)
10 VIEW (cr=1178 pr=0 pw=0 time=76564 us)
10 SORT ORDER BY STOPKEY (cr=1178 pr=0 pw=0 time=76563 us)
500000 TABLE ACCESS FULL SCALAR_T1 (cr=1178 pr=0 pw=0 time=25 us)

######################################################################

  1. 참고 1. 튜닝 사례 : 오라클 성능 고도화 원리와 해법2 (P286 ~ 288) #
    ######################################################################

(3) 두개이상의 값을 리턴하고 싶을때

select d.deptno, d.dname, avg_sal, min_sal, max_sal
from dept d
,(select deptno, avg(sal) avg_sal, min(sal) min_sal, max(sal) max_sal
from emp group by deptno) e
where e.deptno(+) = d.deptno
and d.loc = 'CHICAGO';

DEPTNO DNAME AVG_SAL MIN_SAL MAX_SAL
30 SALES 1566.666666666666666666666666666666666667 950 2850

Execution Plan















---
0 SELECT STATEMENT Optimizer=ALL_ROWS (Cost=7 Card=1 Bytes=72)
1 0 MERGE JOIN (OUTER) (Cost=7 Card=1 Bytes=72)
2 1 TABLE ACCESS (BY INDEX ROWID) OF 'DEPT' (TABLE) (Cost=2 Card=1 Bytes=20)
3 2 INDEX (FULL SCAN) OF 'PK_DEPT' (INDEX (UNIQUE)) (Cost=1 Card=4)
4 1 SORT (JOIN) (Cost=5 Card=3 Bytes=156)
5 4 VIEW (Cost=4 Card=3 Bytes=156)
6 5 HASH (GROUP BY) (Cost=4 Card=3 Bytes=21)
7 6 TABLE ACCESS (FULL) OF 'EMP' (TABLE) (Cost=3 Card=14 Bytes=98)














---

==> 시카고 부서만 대상으로 급여 집계해야하는데 EMP 테이블을 전체 스캔 하는 비효율 존재

select d.deptno, d.dname
,(select avg(sal), min(sal), max(sal) from emp where deptno = d.deptno)
from dept d
where d.loc = 'CHICAGO';

Error: # 913, ORA-00913: 값의 수가 너무 많습니다
SQL Execution Time > 00:00:00.000

==> 문맥상 안맞는 의미 : 스칼라서브 쿼리는 한 레코드당 하나의 값만 리턴

select d.deptno, d.dname
,(select avg(sal) from emp where deptno = d.deptno) avg_sal
,(select min(sal) from emp where deptno = d.deptno) min_sal
,(select max(sal) from emp where deptno = d.deptno) max_sal
from dept d
where d.loc = 'CHICAGO';

DEPTNO DNAME AVG_SAL MIN_SAL MAX_SAL


--


--

-

-

-
30 SALES 1.6e+003 950 2850

Execution Plan















---
0 SELECT STATEMENT Optimizer=ALL_ROWS (Cost=3 Card=1 Bytes=20)
1 0 SORT (AGGREGATE) (Card=1 Bytes=7)
2 1 TABLE ACCESS (FULL) OF 'EMP' (TABLE) (Cost=3 Card=5 Bytes=35)
3 0 SORT (AGGREGATE) (Card=1 Bytes=7)
4 3 TABLE ACCESS (FULL) OF 'EMP' (TABLE) (Cost=3 Card=5 Bytes=35)
5 0 SORT (AGGREGATE) (Card=1 Bytes=7)
6 5 TABLE ACCESS (FULL) OF 'EMP' (TABLE) (Cost=3 Card=5 Bytes=35)
7 0 TABLE ACCESS (FULL) OF 'DEPT' (TABLE) (Cost=3 Card=1 Bytes=20)














---

Predicate information (identified by operation id):















---
2 - filter("DEPTNO"=:B1)
4 - filter("DEPTNO"=:B1)
6 - filter("DEPTNO"=:B1)
7 - filter("D"."LOC"='CHICAGO')














---

==> EMP에서 같은 범위를 반복 억세스 하는 비효율이 발생.

select deptno, dname
, to_number(substr(sal, 1, 7)) avg_sal
, to_number(substr(sal, 8, 7)) min_sal
, to_number(substr(sal, 15)) max_sal
from (
select d.deptno, d.dname
,(select lpad(avg(sal), 7) || lpad(min(sal), 7) || max(sal)
from emp where deptno = d.deptno) sal
from dept d
where d.loc = 'CHICAGO'
);

DEPTNO DNAME AVG_SAL MIN_SAL MAX_SAL
30 SALES 1566.66 950 2850

*************************[Explain Plan Time: 2013/09/04 15:00:10]*************************
Execution Plan















---
0 SELECT STATEMENT Optimizer=ALL_ROWS (Cost=3 Card=1 Bytes=58)
1 0 VIEW (Cost=3 Card=1 Bytes=58)
2 1 TABLE ACCESS (FULL) OF 'DEPT' (TABLE) (Cost=3 Card=1 Bytes=20)














---

Predicate information (identified by operation id):















---
2 - filter("D"."LOC"='CHICAGO')














---

==> 컨캣을 활용하여 하나의 컬럼에 넣은후 substr을 이용하여 원하는 값을 잘라내어 보여줌.

SQL> create or replace type sal_type as object
( avg_sal number, min_sal number, max_sal number ) ;
/

Statement Processed.

SQL> select deptno, dname
, a.sal.avg_sal, a.sal.min_sal, a.sal.max_sal
from (
select d.deptno, d.dname
,(select sal_type( avg(sal), min(sal), max(sal) )
from emp where deptno = d.deptno) sal
from dept d
where d.loc = 'CHICAGO'
) a;

DEPTNO DNAME SAL.AVG_SAL SAL.MIN_SAL SAL.MAX_SAL


--


--

---

---

---
30 SALES 1.6e+003 950 2850

1 rows selected.

*************************[Explain Plan Time: 2013/09/04 15:51:21]*************************
Execution Plan















---
0 SELECT STATEMENT Optimizer=ALL_ROWS (Cost=3 Card=1 Bytes=20)
1 0 SORT (AGGREGATE) (Card=1 Bytes=7)
2 1 TABLE ACCESS (FULL) OF 'EMP' (TABLE) (Cost=3 Card=5 Bytes=35)
3 0 SORT (AGGREGATE) (Card=1 Bytes=7)
4 3 TABLE ACCESS (FULL) OF 'EMP' (TABLE) (Cost=3 Card=5 Bytes=35)
5 0 SORT (AGGREGATE) (Card=1 Bytes=7)
6 5 TABLE ACCESS (FULL) OF 'EMP' (TABLE) (Cost=3 Card=5 Bytes=35)
7 0 TABLE ACCESS (FULL) OF 'DEPT' (TABLE) (Cost=3 Card=1 Bytes=20)














---

Predicate information (identified by operation id):















---
2 - filter("DEPTNO"=:B1)
4 - filter("DEPTNO"=:B1)
6 - filter("DEPTNO"=:B1)
7 - filter("D"."LOC"='CHICAGO')














---

==> SQL이 지저분해지는 것을 방지 하기 위하여 TYPE 오브젝트 활용.

##############################################################

  1. 참고2. 튜닝 사례 - 실핼계획으로 고성능 데이터베이스 튜닝 #
    ##############################################################
  • 스칼라 서브 쿼리는 반드시 반복 수행된다.(인덱스 생성의 예)
    • 요점1. 주테이블의 값을 빠르게 최적화 되게 인덱스 생성이 중요함.
    • 요점2. 스칼라 서브쿼리의 인덱스 생성시 조인키를 반드시 연결 고리(선행인덱스 컬럼)으로 둘 필요가 있음.
  • 스칼라 서브 쿼리의 사용 및 위치에 따라 100배 빠르게 사용하자.
    • 요점1. 스칼라 서브 쿼리를 아우터 조인 쿼리로 변형하는 경우.
    • 요점2. 아우터조인을 스칼라 서브 쿼리로 변형하는 경우

##################################################

  1. 참고 3. 튜닝 사례 - 로지컬 옵티마이저.(SSTS) #
    ##################################################

SELECT A.EMPLOYEE_ID, A.FIRST_NAME , A.LAST_NAME , A.EMAIL
FROM (SELECT E.EMPLOYEE_ID, E.FIRST_NAME, E.LAST_NAME, EMAIL,
(SELECT LOCATION_ID
FROM DEPARTMENTS D
WHERE D.DEPARTMENT_ID = E.DEPARTMENT_ID) AS LOCATION_ID
FROM EMPLOYEES E
WHERE E.JOB_ID = 'IT_PROG') A
WHERE A.LOCATION_ID> 0;

SELECT E.EMPLOYEE_ID, E.FIRST_NAME, E.LAST_NAME, EMAIL,
(SELECT LOCATION_ID
FROM DEPARTMENTS D
WHERE D.DEPARTMENT_ID = E.DEPARTMENT_ID) AS LOCATION_ID
FROM EMPLOYEES E
WHERE E.JOB_ID = 'IT_PROG';

SELECT E.EMPLOYEE_ID, E.FIRST_NAME, E.LAST_NAME ,EMAIL
FROM EMPLOYEES E
WHERE E.JOB_ID = 'IT_PROG'
AND (SELECT LOCATION_ID
FROM DEPARTMENTS D
WHERE D.DEPARTMENT_ID = E.DEPARTMENT_ID) > 0 ;

Predicate Information이완전히 같음을 알 수 있다.

10053 Trace를 분석해보자

검증1.
SELECT /*+ PUSH_SUBQ(@SUB) */
A.EMPLOYEE_ID, A.FIRST_NAME, A.LAST_NAME, A.EMAIL
FROM (SELECT E.EMPLOYEE_ID, E.FIRST_NAME, E.LAST_NAME, EMAIL,
(SELECT /*+ QB_NAME(SUB) */ LOCATION_ID
FROM DEPARTMENTS D
WHERE D.DEPARTMENT_ID = E.DEPARTMENT_ID) AS LOCATION_ID
FROM EMPLOYEES E
WHERE E.JOB_ID = 'IT_PROG' ) A
WHERE A.LOCATION_ID > 0;

검증2.
CREATE INDEX EMP_JOB_DEPT_IX ON EMPLOYEES(JOB_ID, DEPARTMENT_ID);