select * from dept d, emp e
where e.job='MANAGER'
and e.deptno = 10
and d.deptno = e.deptno;
----------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 57 | 2 (0)| 00:00:01 |
| 1 | NESTED LOOPS | | 1 | 57 | 2 (0)| 00:00:01 |
| 2 | TABLE ACCESS BY INDEX ROWID| DEPT | 1 | 20 | 1 (0)| 00:00:01 |
|* 3 | INDEX UNIQUE SCAN | PK_DEPT | 1 | | 0 (0)| 00:00:01 |
| 4 | TABLE ACCESS BY INDEX ROWID| EMP | 1 | 37 | 1 (0)| 00:00:01 |
|* 5 | INDEX RANGE SCAN | EMP_IDX | 2 | | 0 (0)| 00:00:01 |
----------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
3 - access("D"."DEPTNO"=10)
5 - access("E"."DEPTNO"=10 AND "E"."JOB"='MANAGER')
select * from dept d, emp e
where e.job='MANAGER'
and e.deptno = 10
and d.deptno = 10
select * from dept d, empe
where e.job='MANAGER'
and e.deptno = 10
and d.deptno = e.deptno + 0
select *
from 상품이력 a, 주문 b
where b.거래일자 between '20090101' and '20090131'
and a.상품번호 = b.상품번호
and b.거래일자 between a.시작일자 and a.종료일자
---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 44 | 5 (20)| 00:00:01 |
|* 1 | HASH JOIN | | 1 | 44 | 5 (20)| 00:00:01 |
|* 2 | TABLE ACCESS FULL| 상품 | 1 | 25 | 2 (0)| 00:00:01 |
|* 3 | TABLE ACCESS FULL| 주문 | 1 | 19 | 2 (0)| 00:00:01 |
---------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - access("A"."상품번호"="B"."상품번호")
filter("B"."거래일자">="A"."시작일자" AND "B"."거래일자"<="A"."종료일자")
2 - filter("A"."종료일자">='20090101' AND "A"."시작일자"<='20090131')
3 - filter("B"."거래일자">='20090101' AND "B"."거래일자"<='20090131')
h4.튜닝사례1
SELECT IP주소, IP연결일자, 시작IP주소, 종료IP주소
, ISP명, IP등록일자, IP사용기관ID, IP사용기관명, IP사용시도명
, 사용기관주소, 사용기관우편번호, IP책임자명, IP책임자전화번호
FROM IP주소목록
WHERE 시작IP주소 >= :strtIpAddr
AND 종료IP주소 <= :endIpAddr
- 바인드변수(192.168.000 서브넷에 속한 IP주소 목록을 가져오기)
:strtIpAddr := '192.168.000.001'
:endIpAddr := '192.168.000.255'
- 인덱스 상황
CREATE IP주소목록_PK ON IP주소목록(IP주소)
CREATE IP주소목록_X01 ON IP주소목록(시작IP주소)
Call Count CPU Time Elapsed Time Disk Query Current Rows
------- ------ -------- ------------ ---------- ---------- ---------- ----------
Parse 1 0.000 0.00 0 0 0 0
Execute 1 0.000 0.00 0 0 0 0
Fetch 9 32.820 1922.797 341291 6940276 0 106
------- ------ -------- ------------ ---------- ---------- ---------- ----------
Total 11 32.820 1922.797 341291 6940276 0 106
Rows Row Source Operation
------- ----------------------------------------------------
106 TABLE ACCESS BY INDEX ROWID IP주소목록 (cr=6940276 pr=341291 pw=0 ... )
8362619 INDEX RANGE SCAN IP주소목록_X01 (cr=27980 pr=27968 pw=0 time=33450495 us)
- 인덱스를 읽고 테이블 액세스 8,362,619회 발생
- 테이블 랜덤 엑세스 6,912,296 (= 6,940,276-27,980) 회 발생
튜닝방법
1. 인덱스 수정
- 과도한 랜덤 엑세스를 줄이기 위해 IP주소목록_IX01 인덱스에 '종료IP주소' 컬럼 추가
- 인덱스 컬럼 추가에 따른 인덱스 스캔량 증가, 바인드 변수값에 따른 인덱스 스캔량 증가를 막을 수 없음
2. IP주소목록 테이블의 종료IP주소는 시작IP주소보다 크다는 사실을 활용(옵티마이저는 모름!)
SELECT IP주소, IP연결일자, 시작IP주소, 종료IP주소
, ISP명, IP등록일자, IP사용기관ID, IP사용기관명, IP사용시도명
, 사용기관주소, 사용기관우편번호, IP책임자명, IP책임자전화번호
FROM IP주소목록
WHERE 시작IP주소 >= :strtIpAddr
AND 종료IP주소 <= :endIpAddr
AND 시작IP주소 <= 종료IP주소
- 위의 조건을 종합해보면 아래와 같다
:strtIpAddr <= 시작IP주소 <= 종료IP주소 <= :endIpAddr
- 시작 IP주소와 종료 IP주소 컬럼 기준으로 다시 분해하여 조건식을 만든다
WHERE 시작IP주소 BETWEEN :strtIpAddr AND :endIpAddr
AND 종료IP주소 BETWEEN :strtIpAddr AND :endIpAddr
- 바인딩 변수 대입
WHERE 시작IP주소 BETWEEN '192.168.000.001' AND '192.168.000.255'
AND 종료IP주소 BETWEEN '192.168.000.001' AND '192.168.000.255'
- 최종쿼리
SELECT IP주소, IP연결일자, 시작IP주소, 종료IP주소
, ISP명, IP등록일자, IP사용기관ID, IP사용기관명, IP사용시도명
, 사용기관주소, 사용기관우편번호, IP책임자명, IP책임자전화번호
FROM IP주소목록
WHERE 시작IP주소 BETWEEN :strtIpAddr AND :endIpAddr
AND 종료IP주소 BETWEEN :strtIpAddr AND :endIpAddr
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 9 0.000 0.001 0 55 0 106
------- ------ -------- ------------ ---------- ---------- ---------- ----------
Total 11 0.000 0.001 0 55 0 106
Rows Row Source Operation
------- ----------------------------------------------------
0 STATEMENT
106 FILTER (cr=55 pr=0 pw=0 time=37 us)
106 TABLE ACCESS BY INDEX ROWID IP주소목록 (cr=55 pr=0 pw=0 time=34 us)
106 INDEX RANGE SCAN IP주소목록_X01 (cr=12 pr=0 pw=0 time=654 us)
- between으로 풀어서 기술하지 않고 '종료IP주소는 시작IP주소보다 크다'는 사실을 옵티마이저에게 알려만 줘도 옵티마이저가 조건절을 내부적으로 생성해 낸다.
-> 어떻게?
h4.튜닝사례2
아래 쿼리에서 주문상세 테이블에만 상수 조건식이 제공된 것을 볼 수 있다.
select /*+ ordered use_nl(o) use_nl(d) index(o) index(d) */
c.고객명, o.주문일자, o.주문번호, o.배송지, d.상품번호, d.상품가격, d.주문수량
from 고객 c, 주문 o, 주문상세 d
where o.고객번호 = c.고객번호
and d.고객번호 = o.고객번호
and d.주문일자 = o.주문일자
and d.주문번호 = o.주문번호
and d.주문일자 = to_char(sysdate + 1, 'YYYYMMDD');
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 1001 0.125 0.084 0 15098 0 10000
------- ------ -------- ------------ ---------- ---------- ---------- ----------
Total 1003 0.125 0.084 0 15098 0 10000
Rows Row Source Operation
------ -----------------------------------------------------------
10000 TABLE ACCESS BY INDEX ROWID 주문상세 (cr=15Q98 pr=0 pw=0 time=280245 us)
11001 NESTED LOOPS (cr=5098 pr=0 pw=0 time=99435 us)
1000 NESTED LOOPS (cr=2035 pr=0 pw=0 time=20134 us)
10 TABLE ACCESS FULL 고객 (cr=16 pr=0 pw=0 time=156 us)
1000 TABLE ACCESS BY INDEX ROWID 주문 (cr=2019 pr=0 pw=0 time=14212 us)
1000 INDEX RANGE SCAN 주문_PK (cr=1019 pr=0 pw=0 time=6140 us) ......... 1
10000 INDEX RANGE SCAN 주문상세_PK (cr=3063 pr=0 pw=0 time=66267 us) ...... 2
(i) 상수 및 변수에 대한 조건절은 조인문을 타고 다른 쪽 테이블로 전이된다.
하지만 조인문 자체는 전이되지 않는데, 아래 경우가 그것을 말해준다.
select /*+ ordered use_nl(o) use_nl(d) index(o) index(d) */
c.고객명, o.주문일자, o.주문번호, o.배송지, d.상품번호, d.상품가격, d.주문수량
from 고객 c, 주문 o, 주문상세 d
where d.고객번호 = c.고객번호
and d.고객번호 = o.고객번호
and d.주문일자 = o.주문일자
and d.주문번호 = o.주문번호
and d.주문일자 = to_char(sysdate + 1, 'YYYYMMDD');
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 1001 0.188 0.220 0 25052 0 10000
------- ------ -------- ------------ ---------- ---------- ---------- ----------
Total 1003 0.188 0.220 0 25052 0 10000
Rows Row Source Operation
------ -----------------------------------------------------------
10000 TABLE ACCESS BY INDEX ROWID 주문상세 (cr=25052 pr=0 pw=0 time=210132 us)
20001 NESTED LOOPS (cr=15052 pr=0 pw=0 time=260362 us)
10000 NESTED LOOPS (cr=2422 pr=0 pw=0 time=150064 us)
10 TABLE ACCESS FULL 고객 (cr=16 pr=0 pw=0 time=74 us)
10000 TABLE ACCESS BY INDEX ROWID 주문 (cr=2406 pr=0 pw=0 time=90156 us)
10000 INDEX RANGE SCAN 주문_PK (cr=1046 pr=0 pw=0 time=30093 us) ......... 1
10000 INDEX RANGE SCAN 주문상세_PK (cr=12630 pr=0 pw=0 time=167571 us) ...... 2