|
SELECT * FROM DEPT d, EMP e WHERE e.DEPTNO = d.DEPTNO
Execution Plan
----------------------------------------------------------------------------------
0 0 SELECT STATEMENT Optimizer=ALL_ROWS (Cost=4 Card=14 Bytes=868)
1 0 TABLE ACCESS (BY INDEX ROWID) OF ‘EMP’ (TABLE) (Cost=1 Card=4 Bytes=128)
2 1 NESTED LOOPS (Cost=4 Card=14 Bytes=868)
3 2 TABLE ACCESS (FULL) OF ‘DEPT’ (TABLE) (Cost=3 Card=4 Bytes=120)
4 3 INDEX (RANGE SCAN) OF ‘EMP_DEPTNO_IDX’ (INDEX) (Cost=0 Card=5)
|
(_table_lookup_prefetch_size를 0으로 설정하면 전통적인 방식의 NL조인 실행계획으로 되돌아간다.)
- NL조인에서 항상 새 포맷의 실행계획이 나타나는 것은 아니다. 기본적으로 Outer 쪽 인덱스를 Unique Scan 할 때는
작동하지 않는다.
- 새 포맷의 테이블 Prefetch 실행계획이 나타나는 경우
1. Inner 쪽 Non-Unique 인덱스를 Range Scan 할 때는 항상 나타난다.
2. Inner 쪽 Unique 인덱스를 Non-Unique 조건(모든 인덱스 구성 컬럼이 ‘=’ 조건이 아닐 때)으로 Range Scan 할 때도
항상 나타난다.
3. Inner 쪽 Unique 인덱스를 Unique 조건(모든 인덱스 구성 컬럼이 ‘=’ 조건)으로 액세스할 때도 나타날 수 있다.
이때 인덱스는 Range Scan 으로 액세스한다. 테이블 Prefetch 실행계획이 안 나타날 때는 Unique Scan으로 액세스한다.
▶ 세 번째 경우로 테이블 Prefetch가 작동하는 경우 (흔치 않음)
|
SELECT /*+ ORDERED USE_NL(b) CARDINALITY(a 110) */
MAX(a.최종보고일자), AVG(b.주권주식주), COUNT(*)
FROM 지분보고내역 a, 지분보고 b
WHERE b.회사코드 = a.회사코드
AND b.보고서구분코드 = a.보고서구분코드
AND b.최초보고일자 = a.최초보고일자
AND b.보고서id = a.보고서id
AND b.보고일련번호 = a.보고일련번호
AND a.회사코드 BETWEEN ‘000010’ AND ‘00011’
Row Row Source Operation
----------------------------------------------------------------------------------
1 SORT AGGREGATE (cr=1282 pr=0 pw=0)
332 NESTED LOOPS(cr=1282 pr=0 pw=0)
332 TABLE ACCESS BY INDEX ROWID (cr=284 pr=0 pw=0)
332 INDEX RANGE SCAN 지분보고내역_PK (cr=5 pr=384 pw=0)
332 TABLE ACCESS BY INDEX ROWID 지분보고 (cr=998 pr=383 pw=0)
332 INDEX UNIQUE SCAN 지분보고_PK (cr=666 pr=383 pw=0)
|
- CARDINALITY 힌트를 사용해서 드라이빙 집합의 카디널리티를 조금씩 증가 시켜며 테스트했을 때
110까지는 전통적인 방식대로 NL조인 아래쪽에 위치, 111로 지정하는 순간 아래와 같이 실행 계획이 바뀐다. (PK인덱스를
Range Scan 하면서 테이블을 NL조인 위쪽에서 엑세스 하는 것 확인)
|
SELECT /*+ ORDERED USE_NL(b) CARDINALITY(a 111) */ ……
Row Row Source Operation
----------------------------------------------------------------------------------
1 SORT AGGREGATE (cr=1231 pr=0 pw=0)
332 TABLE ACCESS BY INDEX ROWID 지분보고 (cr=1231 pr=0 pw=0)
665 NESTED LOOPS(cr=952 pr=2 pw=0)
332 TABLE ACCESS BY INDEX ROWID (cr=284 pr=0 pw=0)
332 INDEX RANGE SCAN 지분보고내역_PK (cr=5 pr=0 pw=0)
332 INDEX UNIQUE SCAN 지분보고_PK (cr=668 pr=0 pw=0)
|
※ 여러 테스트를 통해서, Inner 쪽 테이블을 Unique 조건으로 액세스할 때 테이블 Prefetch 실행계획이 나타나는 것을 볼 수
있었지만, 저자도 정확한 규칙을 찾지 못함.
(7) 배치 I/O (공식적으로 알려진 바가 없음)
- 11g 에서 시작된 메커니즘
- Outer 테이블로부터 액세스되는 Inner 쪽 테이블 블록에 대한 디스크 I/O Call 횟수를 줄이기 위해 테이블 Prefetch에 이어 추가로 도입된 메커니즘.
- Inner 쪽 인덱스만으로 조인을 한 후, 테이블과의 조인은 나중에 일괄 처리하는 메커니즘인 것으로 추정.
- nlj_batching 힌트 사용. (테이블 Prefetch 방식으로 전환 : no_nlj_batching, nlj_prefetch 힌트 사용)
- 테이블 액세스를 나중에 하지만 부분범위처리는 작동.
(인덱스와의 조인을 모두 완료하고 나서 테이블을 액세스하는 것이 아니라 일정량씩 나누어 처리)
▶ 배치 I/0 방식을 표현한 실행계획
|
Execution Plan
----------------------------------------------------------------------------------
0 SELECT STATEMENT Optimizer=ALL_ROWS (Cost=16 Card=14 Bytes=2K)
1 0 NESTED LOOPS
2 1 NESTED LOOPS (Cost=16 Card=14 Bytes=2K)
3 2 TABLE ACCESS (FULL) OF ‘EMP’ (TABLE) (Cost=2 Card=14 Bytes=1K)
4 2 INDEX (UNIQUE SCAN) OF ‘PK_DEPT’ (INDEX(UNIQUE)) (Cost=0 Card=1)
5 1 TABLE ACCESS (BY INDEX ROWID) OF ‘DEPT’ (TABLE) (Cost=1 Card=1 Bytes=30)
|
아래 설명은 오라클의 공식적인 내용이 아니라, 저자의 경험적 사실들을 설명하려고 세운 가설.
1. 드라이빙 테이블에서 일정량의 레코드를 읽어 Inner 쪽 인덱스와 조인하면서 중간 결과집합을 만든다.
2. 중간 결과집합이 일정량 쌓이면 Inner 쪽 테이블 레코드를 액세스.
이때 테이블 블록을 버퍼 캐시에서 찾으면 바로 최종 결과집합에 담고, 못 찾으면 중간 집합에 남겨 둔다.
3. 2번 과정에서 남겨진 중간 집합에 대한 Inner 쪽 테이블 블록을 디스크로부터 읽는다. 이때 Multiple Single Block I/O
방식을 사용.
4. 버퍼 캐시에 올라오면 테이블 레코드를 읽어 최종 결과 집합에 담는다.
5. 모든 레코드를 처리하거나 사용자가 Fetch Call 을 중단할 때까지 1~4번 과정을 반복한다.
▶ 주목할 점
배치 I/0 방식을 사용할 때, Inner 쪽 테이블 블록이 모두 버퍼 캐시에서 찾아지지 않으면 즉, 실제 배치 I/0가 작동한다면 데이터 정렬 순서가 달라질 수 있다.
모두 버퍼 캐시에서 찾을 때는 이전 메커니즘과 똑 같은 정렬 순서를 보인다.
테이블 Prefetch 방식이나 전통적인 방식으로 NL 조인할 때는 디스크 I/O가 발생하든 안 하든 데이터 정렬 순서가 항상 일정하다.
(8) 버퍼 Pinning 효과
- Prefetch와 배치 I/O 기능이 도입된 시점과 맞물려 버퍼 Pinning 기능에 변화가 생기다 보니 9i와 11g 에서 나타난 NL조인 실행계획 변화를 버퍼 Pinning 효과로 설명하는데 직접적인 연관성 없다.
8i에서 나타난 버퍼 Pinning 효과
- 테이블 블록에 대한 버퍼 Pinning 기능이 작동하기 시작 (단, 하나의 버퍼 블록만 Pinning)
- 이 기능은 NL조인에서 Non-Unique 조건으로 Inner 쪽 테이블을 액세스할 때도 똑같은 작용.
(따라서, Inner쪽 인덱스를 통해 액세스되는 테이블 블록이 계속 같은 블록을 가리키면 논리 I/O가 추가로 발생하지 않는다.)
- 하나의 Outer레코드에 대한 Inner쪽과 조인을 마치고 다른 레코드를 읽기 위해 Outer 쪽으로 돌아오는 순간 Pin을 해제
9i에서 나타난 버퍼 Pinning 효과
- 8i와 같이 테이블 블록 버퍼에 대한 Pinning 작동
- Inner 쪽 인덱스 루트 블록에 대한 버퍼 Pinning 효과가 나타나기 시작 (단, 두 번째 액세스되는 순간 Pinning)
- 9i부터는 Inner 쪽이 Non-Unique인덱스일 때는 테이블 액세스가 항상 NL조인 위쪽으로 올라가므로 항상 버퍼 Pinnig 효과가
나타나는 셈(이때 실행계획 변화를 버퍼 Pinning과 연관시켜 해석하는 오류 범하기 쉽다.)
반면, Inner쪽 Unique 인덱스를 Unique 조건으로 액세스할때는 테이블 액세스가 NL조인 위쪽으로 잘 올라가지 않고,
이때는 Unique 액세스이므로 Inner쪽에서 한 건만 읽고 바로 Outer 테이블 쪽으로 돌아가서 버퍼 Pinning 효과가 나타날 수 없다.
10g에서 나타난 버퍼 Pinning 효과
- Inner 쪽 인덱스 루트 블록과 테이블 블록을 Pinning 하는 기능 + 하나의 Outer 레코드에 대한 Inner 쪽과의 조인을 마치고 Outer 쪽으로 돌아오더라도 테이블 블록에 대한 Pinning 상태를 유지하는 기능 추가
|
DEPT 테이블 4건
T_EMP 테이블 140만건
SELECT /*+ ORDERED USE_NL(d) */ COUNT(e.ENAME), COUNT(d.DNAME)
FROM T_EMP e, DEPT d
WHERE d.DEPTNO = e.DEPTNO
Row Row Source Operation
----------------------------------------------------------------------------------
1 SORT AGGREGATE (cr=1409213 pr=0 pw=0)
1400000 NESTED LOOPS(cr=1409213 pr=0 pw=0)
1400000 TABLE ACCESS BY FULL T_EMP (cr=9211 pr=0 pw=0)
1400000 TABLE ACCESS BY INDEX ROWID DEPT (cr=1400002 pr=0 pw=0)
1400000 INDEX UNIQUE SCAN DEPT_PK (cr=2 pr=0 pw=0)
|
- 인덱스 루트 블록을 버퍼 Pinning 확인 가능
(10g까지는 루트 블록만 Pinning 대상이지만, dept 테이블이 4건짜리 작은 테이블이므로 인덱스 루프 블록이 곧 리프 블록이라서
두 블록 이외에 추가적인 i/o발생하지 않음)
- Inner 쪽을 액세스할 때마다 한 건씩만 읽고 Outer쪽으로 돌아가므로 테이블 블록은 Pinning 효과 나타나지 않음
위와 결과는 같으면서 Unique 인덱스를 Range Scan 하도록,
|
SELECT /*+ ORDERED USE_NL(d) */ COUNT(e.ENAME), COUNT(d.DNAME)
FROM T_EMP e, DEPT d
WHERE d.DEPTNO BETWEEN e.DEPTNO AND e.DEPTNO + 1
Row Row Source Operation
----------------------------------------------------------------------------------
1 SORT AGGREGATE (cr=9214 pr=0 pw=0)
1400000 TABLE ACCESS BY INDEX ROWID DEPT (cr=9214 pr=0 pw=0)
2800001 NESTED LOOPS(cr=9213 pr=0 pw=0)
1400000 TABLE ACCESS FULL T_EMP (cr=9211 pr=0 pw=0)
1400000 INDEX RANGE SCAN DEPT_PK (cr=2 pr=0 pw=0)
|
- Unique 인덱스지만 RANGE SCAN 방식으로 액세스 하였고, 테이블 액세스가 NL조인 위쪽에 위치. 따라서, 140만개 레코드를
모두 처리하는 동안 버퍼 Pinning 상태가 유지됨으로 블록 I/O는 단 1회 발생.
- 인덱스 루트 블록도 여전히 Pinning한 상태로 액세스
11g 에서 나타난 버퍼 Pinning 효과
- User Rowid로 테이블 액세스할 때도 버퍼 Pinning 효과 나타난다.
- NL조인에서 Inner 쪽 루트 아래 인덱스 블록들도 Pinning하기 시작 (배치I/O 기능이 나타남과 동시에 이 기능이 추가되다 보니
NL조인 실행계획 변화가 인덱스 블록에 대한 버퍼 Pinning과 관련이 없다고 오해하기 쉬움)
배치 I/O방식으로 NL조인
|
SELECT /*+ ORDERED USE_NL_WITH_INDEX(d) NLJ_BATCHING(d) */
COUNT(e.ENAME), COUNT(d.DNAME)
FROM T_EMP e, T_DEPT d
WHERE d.NO = e.NO
AND d.DEPTNO = e.DEPTNO
Row Row Source Operation
----------------------------------------------------------------------------------
1 SORT AGGREGATE (cr=14167 pr=0 pw=0)
14000 NESTED LOOPS(cr=14167 pr=0 pw=0)
14000 NESTED LOOPS(cr=167 pr=0 pw=0)
14000 TABLE ACCESS FULL T_EMP (cr=95 pr=0 pw=0)
14000 INDEX UNIQUE SCAN T_DEPT_PK (cr=72 pr=0 pw=0)
14000 TABLE ACCESS BY INDEX ROWID T_DEPT(cr=14000 pr=0 pw=0)
|
- Inner쪽 14000번 액세스하는 동안 T_DEPT_PK 인덱스에 대한 블록 I/O는 72번만 발생 (버퍼 Pinning 작동)
테이블 Prefetch 방식으로 액세스
|
SELECT /*+ ORDERED USE_NL_WITH_INDEX(d) NLJ_PREFETCH(d) */
COUNT(e.ENAME), COUNT(d.DNAME)
FROM T_EMP e, T_DEPT d
WHERE d.NO = e.NO
AND d.DEPTNO = e.DEPTNO
Row Row Source Operation
----------------------------------------------------------------------------------
1 SORT AGGREGATE (cr=14167 pr=0 pw=0)
14000 TABLE ACCESS BY INDEX ROWID T_DEPT (cr=14167 pr=0 pw=0)
28001 NESTED LOOPS(cr=167 pr=0 pw=0)
14000 TABLE ACCESS FULL T_EMP (cr=95 pr=0 pw=0)
14000 INDEX UNIQUE SCAN T_DEPT_PK (cr=72 pr=0 pw=0)
|
- 테이블 Prefetch 방식으로 액세스하더라도 인덱스 블록에 대한 버퍼 Pinning 효과 나타난다.
11g에서 실행계획 변화가 인덱스 블록에 대한 버퍼 Pinning과 관련이 없음을 알 수 있다.
드라이빙 테이블이 무순위로 정렬되도록 (배치 I/0포맷)
|
CREATE TABLE T_EMP
AS
SELECT * FROM SCOTT.EMP, (SELECT ROWNUM NO FROM DUAL CONNECT BY LEVEL <= 1000)
ORDER BY DBMS_RANDOM.VALUE;
SELECT /*+ ORDERED USE_NL_WITH_INDEX(d) NLJ_BATCHING(d) */
COUNT(e.ENAME), COUNT(d.DNAME)
FROM T_EMP e, T_DEPT d
WHERE d.NO = e.NO
AND d.DEPTNO = e.DEPTNO
Row Row Source Operation
----------------------------------------------------------------------------------
1 SORT AGGREGATE (cr=28098 pr=0 pw=0)
14000 NESTED LOOPS(cr=28098 pr=0 pw=0)
14000 NESTED LOOPS(cr=14098 pr=0 pw=0)
14000 TABLE ACCESS FULL T_EMP (cr=94 pr=0 pw=0)
14000 INDEX UNIQUE SCAN T_DEPT_PK (cr=14004 pr=0 pw=0)
14000 TABLE ACCESS BY INDEX ROWID T_DEPT(cr=14000 pr=0 pw=0)
|
- 인덱스 블록에 대한 버퍼 Pinning효과가 사라짐. (드라이빙 테이블을 무순위로 정렬한 것이 원인)
- 인덱스 블록도 버퍼 Pinning 효과가 나타나려면 같은 값으로 반복 액세스 해야 함.
- 테이블 Prefetch 포맷을 사용해도 마찬가지
오라클 고도화 원리와 해법 2 (bysql.net 2011년 1차 스터디)
작성자: 이주영 (suspace)
최초작성일: 2011년 3월 28일
본문서는 bysql.net 스터디 결과입니다 .본 문서를 인용하실때는 출처를 밝혀주세요. http://www.bysql.net
문서의 잘못된 점이나 질문사항은 본문서에 댓글로 남겨주세요. ^^