3. 다양한 인덱스 스캔 방식
2011.02.18 22:03
03.다양한 인덱스
?
No |
인덱스 스캔 방식 |
사용하는 Hint |
비고 |
1 |
Index Range Scan |
/*+ Index(Table Alias, Index Name) */ |
|
2 |
Index Full Scan |
Index( Full Scan) 형태로 표현 | |
3 |
Index Unique Scan |
/*+ Index(Table Alias, Index Name) */ |
|
4 |
Index Skip Scan |
/*+ Index_ss(Table Alias, Index Name) */ |
|
5 |
Index Fast Full Scan |
/*+ Index_fss(Table Alias, Index Name) */ |
|
6 |
Index Range Scan Descending |
/*+ Index_desc(Table Alias, Index Name) */ |
|
7 |
And-Equal Index Combine Index Join |
/*+ And_Equal(Table Alias, Index Name, ...) */ /*+ Index_Combine(Table Alias, Index Name, ...) */ /*+ Index_Join(Table Alias, Index Name, ...) */ |
And-Equal (10g에서 폐기) |
(1)Index Range Scan
-루트 블럭에서 리프 블럭까지 수직적으로 탐색한 후에 리프 블럭을 필요한 부분만 스캔하는 방식
-가장 일반적으로 정상적인 형태의 액세스 방식
-일반적인 실행계획
CREATE INDEX EMP_DEMPNO_IDX ON EMP (DEPTNO);
SELECT * FROM EMP WHERE DEPTNO = 20;
----------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 5 | 190 | 2 (0)| 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID| EMP | 5 | 190 | 2 (0)| 00:00:01 |
|* 2 | INDEX RANGE SCAN | EMP_DEMPNO_IDX | 5 | | 1 (0)| 00:00:01 |
----------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("DEPTNO"=20)
-실행계획에 Index Range이 나타난다고 항상 빠른 속도를 보장하는 것은 아님
▶스캔하는 범위(Range)를 얼마만큼 줄일 수 있는냐가 관건임
▶인덱스 설계와 SQL 튜닝의 핵심원리 중 하나임
▶인덱스의 선두 컬럼이 사용되어야 하며 그렇지 못한 상황에서 인덱스를 강제로 사용하기 위해
힌트를 사용한다면 Index Full Scan 방식으로 처리됨
(2)Index Full Scan
-수직적 탐색없이 인덱스 리프블럭을 처음부터 끝까지 수평적으로 탐색하는 방식
-데이터 검색을 위한 최적의 인덱스가 없을 경우 차선으로 선택됨
-일반적인 실행계획
CREATE INDEX EMP_IDX ON EMP (ENAME, SAL);
SELECT * FROM EMP WHERE SAL > 2000 ORDER BY ENAME;
---------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 10 | 380 | 2 (0)| 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID| EMP | 10 | 380 | 2 (0)| 00:00:01 |
|* 2 | INDEX FULL SCAN | EMP_IDX | 10 | | 1 (0)| 00:00:01 |
---------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("SAL">2000)
filter("SAL">2000)
-Index Full Scan의 효율성
▶선두 컬럼(ename)이 조건절이 없으면 옵티마이져는 우선적으로 Table Full Scan을 고려함
①대용량 테이블이어서 Table Full Scan의 부담이 크다면 인덱스 활용방법을 고려함
②인덱스 스캔단계에서 대부분 레코드를 필터링하고 일부에 대해서만 테이블 액세스하는
방식이 유리한 경우를 고려
③이 경우는 옵티마이져가 Index Full Scan 방식을 선택함
④위의 예제(실제 실행계획) : Index Full Scan VS Table Full Scan
- Index Full Scan
----------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)|
----------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | | 2 (100)|
| 1 | TABLE ACCESS BY INDEX ROWID| EMP | 1 | 38 | 2 (0)|
|* 2 | INDEX FULL SCAN | EMP_IDX | 1 | | 1 (0)|
----------------------------------------------------------------------------
- Table Full Scan
----------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)|
----------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | | 4 (100)|
| 1 | SORT ORDER BY | | 1 | 38 | 4 (25)|
|* 2 | TABLE ACCESS FULL | EMP | 1 | 38 | 3 (0)|
----------------------------------------------------------------------------
-Index를 활용한 소트 연산 대체
▶Index Full Scan 또한 Index Range Scan과 마찬가지로 그 결과집합이 인덱스컬럼 순으로
정렬되므로 Sort Order By 연산을 생략할 목적으로 사용될 수 있음(위 PLAN 참고)
▶FIRST_ROWS HINT를 사용해서 옵티마이져는 소트연산을 생략하고 전체 집합 중 일부만
빠르게 리턴할 목적으로 사용할 경우에 사용할 수 있음
▶단, 처음 의도와는 다르게 처음부터 끝까지 FETCH 한다면 TABLE FULL SCAN 한 것보다
훨씬 더 많은 I/O를 일으키고 자원을 낭비하는 결과를 가져옴
(3)Index Unique Scan
-인덱스를 통해 '=' 조건으로 탐색하는 경우에 작동
SELECT /*+ GATHER_PLAN_STATISTICS */ * FROM EMP S WHERE EMPNO = 7788;
--------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 38 | 1 (0)| 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID| EMP | 1 | 38 | 1 (0)| 00:00:01 |
|* 2 | INDEX UNIQUE SCAN | PK_EMP | 1 | | 0 (0)| 00:00:01 |
--------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("EMPNO"=7788)
-인덱스를 'BETWEEN' 조건으로 탐색하는 경우에는 Index Range Scan을 작동
--------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 5 | 190 | 2 (0)| 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID| EMP | 5 | 190 | 2 (0)| 00:00:01 |
|* 2 | INDEX RANGE SCAN | PK_EMP | 5 | | 1 (0)| 00:00:01 |
--------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("EMPNO">=7788)
(4)Index Skip Scan
-Oracle 9i에서 처음 보인 기능
-인덱스의 선두 컬럼의 Discint Value 개수가 적고 후행 컬럼의 분포도가 좋을 때 유용함
-Index Skip Scan이 작동하기 위한 조건(교재 참고)
-In-List Iterator와의 비교
▶사용용도는 같지만 동작원리는 분명히 다름(교재 참고)
(5)Index Fast Full Scan
-Index Fast Full Scan은 Index Full Scan 보다 빠르다. 이유는 인덱스 구조를 무시하고
인덱스 세그먼트 전체를 Multiblock Read 방식으로 스캔하기 때문이다.
CREATE TABLE BIG_TABLE
AS
SELECT ROWNUM AS NO
, LVL AS PART
, OBJECT_NAME
FROM DBA_OBJECTS
, (SELECT LEVEL LVL FROM DUAL CONNECT BY LEVEL < 11);
CREATE UNIQUE INDEX BIG_TABLE_UI ON BIG_TABLE(NO);
CREATE INDEX BIG_TABLE_IDX ON BIG_TABLE(OBJECT_NAME);
-Index Full Scan VS Index Fast Full Scan
(SYSTEM 계정으로) ALTER SYSTEM FLUSH BUFFER_CACHE;
SELECT /*+ INDEX(B, BIG_TABLE_IDX) */ COUNT(OBJECT_NAME) FROM SCOTT.BIG_TABLE B;
-----------------------------------------------------------------------------------
| Id | Operation | Name | E-Rows |E-Bytes| Cost (%CPU)| E-Time |
-----------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | | 3676 (100)| |
| 1 | SORT AGGREGATE | | 1 | 66 | | |
| 2 | INDEX FULL SCAN| BIG_TABLE_IDX | 800K| 50M| 3676 (1)| 00:00:45 |
-----------------------------------------------------------------------------------
(SYSTEM 계정으로) ALTER SYSTEM FLUSH BUFFER_CACHE;
SELECT /*+ INDEX_FFS(B, BIG_TABLE_IDX) */ COUNT(OBJECT_NAME) FROM SCOTT.BIG_TABLE B;
----------------------------------------------------------------------------------------
| Id | Operation | Name | E-Rows |E-Bytes| Cost (%CPU)| E-Time |
----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | | 1001 (100)| |
| 1 | SORT AGGREGATE | | 1 | 66 | | |
| 2 | INDEX FAST FULL SCAN| BIG_TABLE_IDX | 800K| 50M| 1001 (1)| 00:00:13 |
----------------------------------------------------------------------------------------
특징 요약 - 표참고
Index Full Scan |
Index Fast Full Scan |
1.인덱스 구조를 따라 스캔 |
1.세그먼트 전체를 스캔 |
2.결과집합 순서보장 |
2.결과집합 순서보장 안 됨 |
3.Single Block I/O |
3.MultiBlock I/O |
4.병렬스캔 불가(파티션 돼 있지 않다면) |
4.병렬스캔 가능 |
5.인덱스에 포함되지 않은 컬럼 조회 시에도 사용가능 |
5.인덱스에 포함된 컬럼으로만 조회할 때 사용 가능 |
-튜닝사례(교재와는 다름)
(튜닝 전)
SELECT * FROM BIG_TABLE WHERE OBJECT_NAME LIKE '%PK_EMP%';
--------------------------------------------------------------------------------
| Id | Operation | Name | E-Rows |E-Bytes| Cost (%CPU)| E-Time |
--------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | | 1048 (100)| |
|* 1 | TABLE ACCESS FULL| BIG_TABLE | 42 | 3864 | 1048 (1)| 00:00:13 |
--------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter(("OBJECT_NAME" IS NOT NULL AND "OBJECT_NAME" LIKE '%PK_EMP%'))
(튜닝 후) - 책과는 좀 다름. 책이 오타가 있음
SELECT /*+ ORDERED USE_BL(B) NO_MERGE(B) ROWID(B) */ B.*
FROM (
SELECT /*+ INDEX_FFS(A, BIG_TABLE_IDX) */ ROWID RID
FROM BIG_TABLE A
WHERE OBJECT_NAME LIKE '%PK_EMP%'
) A
, BIG_TABLE B
WHERE B.ROWID = A.RID;
----------------------------------------------------------------------------------------------
| Id | Operation | Name | E-Rows |E-Bytes| Cost (%CPU)| E-Time |
----------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | | 1044 (100)| |
| 1 | NESTED LOOPS | | 42 | 4872 | 1044 (1)| 00:00:13 |
| 2 | VIEW | | 42 | 504 | 1001 (1)| 00:00:13 |
|* 3 | INDEX FAST FULL SCAN | BIG_TABLE_IDX | 42 | 3276 | 1001 (1)| 00:00:13 |
| 4 | TABLE ACCESS BY USER ROWID| BIG_TABLE | 1 | 104 | 1 (0)| 00:00:01 |
----------------------------------------------------------------------------------------------
(6)Index Range Scan Descending
-Index Range Scan 방식과 동일한 스캔방식
(7)And-Equal / Index Combine / Index Join
-오라클에서 제공하는 두 개 이상 인덱스를 함께 사용하는 방법들
▶And-Equal
8i에서 도입 / 10g에서 폐기됨 - 11g에서 실제 안됨
SELECT /*+ AND_EQUAL(E, EMP_DEPTNO_IDX, EMP_JOB_IDX) */ *
FROM EMP E
WHERE DEPTNO = 30
AND JOB = 'SALESMAN';
-------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 38 | 2 (0)| 00:00:01 |
|* 1 | TABLE ACCESS BY INDEX ROWID| EMP | 1 | 38 | 2 (0)| 00:00:01 |
|* 2 | INDEX RANGE SCAN | EMP_JOB_IDX | 3 | | 1 (0)| 00:00:01 |
-------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter("DEPTNO"=30)
2 - access("JOB"='SALESMAN')
▶Index Combine
SELECT /*+ INDEX_COMBINE(E, EMP_DEPTNO_IDX, EMP_JOB_IDX) */ *
FROM EMP E
WHERE DEPTNO = 30
AND JOB = 'SALESMAN';
---------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 38 | 3 (0)| 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID | EMP | 1 | 38 | 3 (0)| 00:00:01 |
| 2 | BITMAP CONVERSION TO ROWIDS | | | | | |
| 3 | BITMAP AND | | | | | |
| 4 | BITMAP CONVERSION FROM ROWIDS| | | | | |
|* 5 | INDEX RANGE SCAN | EMP_JOB_IDX | | | 1 (0)| 00:00:01 |
| 6 | BITMAP CONVERSION FROM ROWIDS| | | | | |
|* 7 | INDEX RANGE SCAN | EMP_DEMPNO_IDX | | | 1 (0)| 00:00:01 |
---------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
5 - access("JOB"='SALESMAN')
7 - access("DEPTNO"=30)
①Index Combine은 And-Equal과 마찬가지로 분포도가 좋지 않은 두개 이상의 인덱스를 결합하여
테이블 Random 를 줄이는 데 목적이 있다.
②조건절이 '='이어야 할 필요가 없고, Non-Unique 인덱스일 필요가 없다. 게다가 Bitmap 인덱스를
이용하므로 조건절이 OR로 결합된 경우에도 유용하다.
▶Index Join
①테이블간 조인에 사용되는 것과 무관하게 한 테이블에 속한 여러 인덱스를 이용해 테이블 액세스
없이 결과 집합을 만들 때 사용하는 인덱스 스캔방식이다.
②Hash Join 메카니즘을 사용한다.
③쿼리에 사용된 컬럼들이 인덱스에 모두 포함될 때만 작동한다. 모두 포함될 필요는 없고
둘 중 어느 한쪽에 포함되기만 하면 된다.
④index$_join$_xxx 의 형태로 PLAN에 표기된다.
SELECT /*+ INDEX_JOIN(E, EMP_DEPTNO_IDX, EMP_JOB_IDX) */ DEPTNO, JOB
FROM EMP E
WHERE DEPTNO = 30
AND JOB = 'SALESMAN';
---------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 11 | 3 (34)| 00:00:01 |
|* 1 | VIEW | index$_join$_001 | 1 | 11 | 3 (34)| 00:00:01 |
|* 2 | HASH JOIN | | | | | |
|* 3 | INDEX RANGE SCAN| EMP_JOB_IDX | 1 | 11 | 1 (0)| 00:00:01 |
|* 4 | INDEX RANGE SCAN| EMP_DEMPNO_IDX | 1 | 11 | 1 (0)| 00:00:01 |
---------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter("JOB"='SALESMAN' AND "DEPTNO"=30)
2 - access(ROWID=ROWID)
3 - access("JOB"='SALESMAN')
4 - access("DEPTNO"=30)
- 오라클 고도화 원리와 해법 2 (bysql.net 2011년 1차 스터디)
- 작성자: 멋진넘
- 최초작성일: 2011년 2월 20일
- 본문서는 bysql.net 스터디 결과입니다 .본 문서를 인용하실때는 출처를 밝혀주세요. http://www.bysql.net
- 문서의 잘못된 점이나 질문사항은 본문서에 댓글로 남겨주세요. ^^
댓글 0
번호 | 제목 | 글쓴이 | 날짜 | 조회 수 |
---|---|---|---|---|
40 | 2. 서브쿼리 Unnesting | darkbeom | 2011.05.15 | 19841 |
39 | 2. 소트를 발생시키는 오퍼레이션 | 휘휘 | 2011.06.12 | 3508 |
38 | 2. 파티션 Pruning | 실천하자 | 2011.06.21 | 26102 |
37 |
3. 병렬 조인
![]() | 실천하자 | 2011.06.28 | 7220 |
» |
3. 다양한 인덱스 스캔 방식
![]() | 멋진넘 | 2011.02.18 | 33881 |
35 |
3. 해시 조인
![]() | darkbeom | 2011.03.20 | 21602 |
34 | 3. 옵티마이저의 한계 | 멋진넘 | 2011.04.19 | 7902 |
33 | 3. 뷰 Merging | 실천하자 | 2011.05.15 | 23516 |
32 |
3. 데이터 모델 측면에서의 검토
![]() | 멋진넘 | 2011.06.13 | 5922 |
31 | 3. 인덱스 파티셔닝 | darkbeom | 2011.06.19 | 54156 |
30 |
4. 테이블 Random 액세스 부하
[1] ![]() | darkbeom | 2011.02.23 | 14754 |
29 | 4. 조인 순서의 중요성 | 운영자 | 2011.03.27 | 14374 |
28 | 4. 통계정보 Ⅰ | darkbeom | 2011.04.25 | 18214 |
27 | 4. 조건절 Pushing | 실천하자 | 2011.05.31 | 7114 |
26 |
4. 소트가 발생하지 않도록 SQL 작성
![]() | 멋진넘 | 2011.06.12 | 7940 |
25 | 5. 테이블 Random 액세스 최소화 튜닝 | suspace | 2011.02.24 | 7028 |
24 | 6. 스칼라 서브쿼리를 이용한 조인 [1] | 휘휘 | 2011.03.28 | 6686 |
23 | 5. Outer 조인 | 실천하자 | 2011.03.29 | 5084 |
22 | 5. 카디널리티 | suspace | 2011.04.26 | 5989 |
21 | 5. 조건절 이행 | 휘휘 | 2011.05.29 | 5509 |