본문 바로가기
정리

[친절한 SQL 튜닝] 인덱스 기본

by baau 2024. 4. 21.

작년에 면접 준비를 하면서 DB 인덱스에 대해서 정리한 내용이 있다. https://minnseong.tistory.com/19

아래 내용보다는 "친절한 SQL 튜닝"을 읽으면서 실무적인 내용과 새로운 개념을 배울 수 있어 책을 읽어보는 것도 추천한다!

 

[DB] Index 정리

Index 추가적인 쓰기 작업과 저장 공간을 활용하여 데이터베이스의 검색 속도를 향상하기 위한 자료구조 Index를 통해 검색 속도 향상과 조회 성능을 높일 수 있다. Index를 사용하지 않는 칼럼을 조

minnseong.tistory.com

 

01. 인덱스

  • 대용량 테이블에서 필요한 데이터만 빠르게 효율적으로 액세스 하기 위해 사용하는 오브젝트
  • 인덱스가 정렬되어 있기 때문에 범위 스캔이 가능하다.
  • 일반적으로 B+ 트리 인덱스를 사용한다.

 

02. B+ 트리

  • 루트와 브랜치 블록에는 키값을 갖지 않는 LMC(가장 왼쪽 첫 번째 레코드)가 있다.
  • LMC : 자식 노드 중 가장 왼쪽 끝에 위치하는 블록을 가르킨다.
  • 리프 블록에 저장된 각 레코드는 키 값 순으로 정렬되어 있으며, 테이블 레코드를 가리키는 주소값, ROWID를 갖는다.

 

인덱스 튜닝의 핵심 요소
1. 인덱스 스캔에서 발생하는 비효율을 줄이는 것 
2. 테이블 액세스 횟수를 줄이는 것, 인덱스 스캔 이후 테이블 레코드를 엑세스할 때 랜덤 I/O 방식을 사용하므로 '랜덤 엑세스 최소화 튜닝'이라고 할 수 있다.

cf. 모든 디비 성능 문제는 랜덤 I/O를 줄이는 데에 있다. NL 조인이 대량 데이터 조인을 할 때 느린 이유도 랜덤 I/O 때문이다.

 

03. 인덱스 탐색 과정

수직적 탐색

  • 인덱스 스캔 시작 지점을 찾는 과정
  • 정렬된 인덱스 레코드 중 조건을 만족하는 첫 번째 레코드를 찾는 과정
  • 루트 블록에서부터 시작

수평적 탐색

  • 데이터를 찾는 과정
  • 수직적 탐색을 통해 스캔 시작점을 찾은 다음, 찾고자 하는 데이터가 더 나타나지 않을 때까지 인덱스 리프 블록을 수평적으로 스캔한다.
  • 인덱스 리프 블록은 앞뒤 블록에 대한 주소값을 갖고 있어 수평적 탐색이 가능하다. (양방향 연결 리스트)
  • 조건절을 만족하는 데이터를 모두 찾고, ROWID를 얻기 위함이다.
  • 실제 필요한 칼럼을 인덱스가 모두 갖고 있어 인덱스 스캔만 하는 경우도 있지만, 일반적으로 인덱스를 스캔한 이후에는 테이블을 액세스가 필요하기 때문에 ROWID를 얻어야 한다.

 

04. Range Scan이 불가능한 경우

아래 경우 모두 인덱스 스캔 시작점을 찾을 수 없기 때문에 인덱스를 사용할 수 없다.

 

1. 인덱스 칼럼은 가공하지 않아야 한다.

ex) where substr(birthday, 5, 2) = '05'

where nvl(orderCnt, 0) < 100

 

2. LIKE로 중간 값을 검색하지 않아야 한다.

ex) where 업체명 like '%대한%'

 

3. OR 조건

ex) where (전화번호 =: tel_no OR 고객명 =: cust_nm)

위의 경우 OR Expansion을 유도하여 Index Range Scan 이 가능하다.

/** Index Range Scan 불가능 */
SELECT * 
FROM 고객 
WHERE 전화번호 = :tel_no 
OR 고객명 = :cust_nm;

-----------------------------

/** Index Range Scan 가능 (OR Expansion) */
SELECT * 
FROM 고객 
WHERE 고객명 = :cust_nm

UNION ALL

SELECT * 
FROM 고객 
WHERE 전화번호 = :tel_no
AND (고객명 <> :cust_nm OR 고객명 IS NULL)

 

use_concat 힌트를 이용해 OR Expansion을 유도할 수 있다.

/** Index Range Scan 가능 (OR Expansion) */

SELECT /*+ use_concat */ 
FROM 고객
WHERE 전화번호 = :tel_no 
OR 고객명 = :cust_nm

 

4. IN 조건절

ex. where 전화번호 in ( :tel_no1, :tel_no2 )

IN절은 OR 조건을 다른 방식으로 표현한 것이기 때문에, OR처럼 UNION ALL 방식으로 Index Range Scan이 가능하게 할 수 있다.

SELECT * 
FROM 고객 
WHERE 전화번호 = :tel_no1

UNION ALL

SELECT * 
FROM 고객 
WHERE 전화번호 = :tel_no2

 

IN 조건절에 대해서는 Optimizer가 IN-List Iterator 방식을 사용하여 IN-LIST 개수만큼 Index Range Scan을 반복한다.

 

5. 다중 인덱스에서 가장 첫 번째 조건은 인덱스 선두 칼럼이어야 한다.

ex. 인덱스 : 소속팀 + 사원명 + 연령

SELECT *
FROM 사원
WHERE 사원명 = '홍길동';

 

인덱스 스캔 시작점을 찾을 수 없기 때문에 인덱스 리프 블록을 처음부터 끝까지 모두 스캔해야 한다.

 

하지만, 인덱스를 잘 탔다고 해서 튜닝은 끝이 아니다.. 인덱스 리프 블록에서 스캔하는 양을 따져봐야 할 수 있다.. 해당 내용은 다음 장에서 알아볼 수 있을 것 같다!

 

05. 인덱스를 이용한 소트 연산 생략

  • 인덱스가 정렬되어 있는 성질을 이용하여 옵티마이저는 ORDER BY가 있어도 정렬 연산을 따로 수행하지 않는다.
  • 인덱스를 이용하여 소트 연산이 생략되어 있을 경우, 실행계획을 확인하면 SORT ORDER BY 연산이 없음을 확인할 수 있다.
  • 내림차순 정렬에도 인덱스를 활용할 수 있다. 인덱스 리프 블록은 양방향 연결 리스트 구조이기 때문에 가능하다.
  • 최솟값과 최댓값을 구할 때 마찬가지이다.

 

06. 자동 형변환

SELECT *
FROM 고객
WHERE 생년월일 = 19980803;

 

위 경우, 옵티마이저는 Index Range Scan이 아닌 Table Full Scan을 선택한다.

왜냐하면 생년월인은 문자형인데, 비교값이 숫자형이기 때문에 자동으로 형변환이 일어난 것이다. 인덱스 칼럼을 가공한 셈이다.

SELECT *
FROM 고객
WHERE TO_NUMBER(생년월일) = 19980803;

 

오라클에서는

  • 숫자형과 문자형은 순자형 기준으로 문자형에서 형변환이 일어난다.
  • LIKE 에는 문자형 기준으로 숫자형 칼럼이 형변환된다.
  • 날짜형과 문자형은 날짜형 기준으로 문자형이 형변환된다. (날짜 포맷을 명확히 지정해 주는 코딩 습관이 중요하다.)
자동 형변환에 의존하지 않고, 인덱스 칼럼 기준으로 반대편 칼럼 또는 값을 정확히 형변환해 주는 것이 중요하다. 연산 횟수를 줄이기 위해서 TO_CHAR, TO_DATE 같은 형변환을 의도적으로 생략하곤 하는데, SQL 성능은 그런 곳에서 결정되지 않고 블록 I/O를 줄일 수 있느냐 없느냐에서 결정된다. 애초에 형변환 함수를 생략한다 하더라도 옵티마이저가 자동으로 형변환 함수를 생성하기 때문에 연산 횟수를 줄이는 것도 아니다.

 

07. 스캔 방식의 주요 특징

[이미지 출처] https://developer-guide.com/

 

Index Range Scan

  • B+ 트리 인덱스의 가장 일반적이고 정상적인 형태의 액세스 방식
  • 인덱스 루트에서 리프 블록까지 수직적 탐색한 이후에 수평적 탐색을 통해 필요한 범위만 스캔하는 방식이다.
  • 실행 계획 : INDEX (RANGE SCAN)..

 

Index Full Scan

    • 수직적 탐색 없이 인덱스 리프 블록을 처음부터 끝까지 수평적으로 탐색하는 방식
    • 실행 계획 : INDEX (FULL SCAN)...
    • 데이터 검색을 위한 최적의 인덱스가 없을 때 차선으로 선택된다. (인덱스 선두 칼럼이 조건절에 없는 경우 자주 사용)
    • 데이터 저장공간은 '칼럼 길이 * 레코드 수'에 의해 결정되는데, 인덱스가 차지하는 면적은 테이블보다 훨씬 작다. 만약 인덱스 스캔 단계에서 대부분 레코드를 필터링하고 아주 일부만 테이블을 액세스 하는 상황이라면 인덱스를 스캔하는 것이 유리하다.
    • 전체 중에 극히 일부인 데이터를 찾는 경우 유리하다. 그렇지 않으면 인덱스 스캔을 하더라도 거의 모든 레코드에 대해 테이블 액세스가 발생하기 때문에 Table Full Scan 보다 오히려 불리하다.

 

Index Unique Scan

  • 수직적 탐색으로만 데이터를 찾는 스캔 방식
  • Unique 인덱스를 '=' 조건으로 탐색하는 경우 동작한다.
  • Unique 인덱스가 존재하는 칼럼은 중복 값이 입력되지 않게, DBMS가 데이터 정합성을 관리하기 때문에 데이터를 한건 찾으면 더 이상 탐색할 필요가 없다.

 

Index Skip Scan

  • 인덱스 선두 칼럼이 조건절에 없어도 인덱스를 활용하는 새로운 스캔 방식
  • 루트 또는 브랜치 블록에서 읽는 칼럼 값 정보를 이용해 조건절에서 부합하는 레코드를 포함할 가능성이 있는 리프 블록만 골라서 액세스 하는 방식
  • 조건절에 빠진 인덱스 선두 칼럼의 Distinct Value 개수가 적고 후행 칼럼의 Distinct Value 개수가 많을 경우 유리하다.
    • Distinct Value 개수가 적다는 것은 카디널리티가 낮은(중복도가 높은) 성별 같은 것이고, Distinct Value 개수가 많다는 것은 카디널리티가 높은(중복도가 낮은) 고객번호 같은 것이다.
  • 작동하기 위한 조건
    • 인덱스 칼럼이 [업종유형코드 + 업종코드 + 기준일자] 일경우,
      조건절에 [업종유형코드, 기준일자]로 있으면 Index Range Scan을 할 수 있지만, 업종유형코드가 같은 구간을 모두 스캔해야 한다. 하지만 Index Skip Scan을 사용한다면 업종유형코드 구간에서 기준일이 포함할 가능성이 있는 리프 블록만 골라서 액세스 가능하다.
    • Distinct Value 가 적은 두 개의 선두 칼럼이 모두 조건절에 없는 경우도 유용하게 사용된다.
    • 선두 칼럼이 부등호, Between, LIKE 같은 범위검색 조건일 때도 사용한다.

 

Index Fast Full Scan

  • Index Full Scan 보다 빠르다.
  • 논리적인 인덱스 트리 구조를 무시하고 인덱스 세그먼트 전체를 Multiblock I/O 방식으로 스캔하기 때문이다. (물리적으로 디스크에 저장된 순서대로 인덱스 리프 블록을 읽는다.)
  • 관련 힌트 : index_ffs, no_index_ffs
  • Multiblock I/O 방식을 사용하므로 디스크로부터 대량의 인덱스 블록을 읽어야 할 때 큰 효과를 발휘한다.
  • 인덱스 리프 노드가 갖는 연결 리스트 구조를 무시한 채 데이터를 읽기 때문에 결과집합이 인덱스 키 순서대로 정렬되지 않는다.
  • 쿼리에 사용한 칼럼이 모두 인덱스에 포함돼 있을 때만 사용할 수 있다는 점을 기억해야 한다.
  • 인덱스가 파티션 있지 않더라도 병렬 쿼리가 가능한 것도 중요한 특징이다. 병렬 쿼리시에는 Direct Path I/O 방식을 사용하기 때문에 I/O 속도가 빨라진다.

 

Index Rage Scan Descending

  • Index Range Scan과 기본적으로 동일한 스캔 방식
  • 인덱스를 뒤에서부터 앞쪽으로 스캔하기 때문에 내림차순으로 정렬된 결과집합을 얻는다는 점만 다르다.
  • 내림차순 정렬하고자 할 때 옵티마이저가 알아서 인덱스를 거꾸로 읽는 실행계획을 수립하고, 그렇지 않는다면 index_desc 힌트를 이용해 유도할 수 있다.
  • MAX 값을 구하고자할 떄도 자동으로 이루어진다.

 

책 읽고 정리하는 과정
1. '친절한 SQL 튜닝' 책을 정독하며, 중요한 부분 바로바로 타이핑
2. 한 챕터가 끝나면, 타이핑한 부분을 복습하며 관련 구글링 및 재정리

 

더보기

중요 요점) 인덱스 탐색 과정이 수직적 탐색과 수평적 탐색, 두 단계로 이루어진 사실.

 

인덱스 : 대용량 테이블에서 필요한 데이터만 빠르게 효율적으로 액세스 하기 위해 사용하는 오브젝트 (책 색인)

- 인덱스가 정렬되어 있기 때문에 범위 스캔 (Range Scan) 이 가능하다.

- 일반적으로 B+ 트리 인덱스를 사용한다. (어떤 값을 탐색하더라도 인덱스 루트에서 리프블록에 도달하기까지 읽는 블록수는 동일하다. = Balanced) 

    - 루트와 브랜치 블록에는 키값을 갖지 않은 특별한 레코드가 하나 있다. (가장 왼쪽 첫 번째 레코드, LMC (Leftmost Child) 자식 노드 중 가장 왼쪽 끝에 위치한 블록을 가리킨다.)

    - 리프 블록에 저장된 각 레코드는 키값 순으로 정렬돼 있을 뿐만 아니라 테이블 레코드를 가리키는 주소값, ROWID를 갖는다.

 

테이블에서 데이터를 찾는 방법

1) 테이블 전체 스캔 2) 인덱스 이용

 

인덱스 튜닝의 두 가지 핵심요소

1) 인덱스 스캔 과정에서 발생하는 비효율을 줄이는 것 (인덱스 스캔 효율화 튜닝)

2) 테이블 액세스 횟수를 줄이는 것, 인덱스 스캔 후 테이블 레코드를 액세스할 때 랜덤 IO 방식을 사용하므로 '랜덤 엑세스 최소화 튜닝'이라고 할 수 있음 (1번보다 더 중요)

 

알아두면 좋은 것, 이후에 나오는 애기

모든 디비 성능 문제는 랜덤 I/O 를 줄이는 데에 있다, NL 조인이 대량 데이터 조인할 때 느린 이유도 랜덤 I/O 때문이다.

- 소트 머지 조인과 해시 조인이 개발됐으므로 이들 조인 메소드도 결국 느린 랜덤 I/O를 극복하기 위해서이다.

 

인덱스 탐색 과정

- 수직적 탐색 : 인덱스 스캔 시작지점을 찾는 과정

: 정렬된 인덱스 레코드 중 조건을 만족하는 첫 번째 레코드를 찾는 과정 (인덱스 스캔 시작 지점을 찾는 과정)

: 루트 블록에서부터 시작

 

- 수평적 탐색 : 데이터를 찾는 과정

: 수직적 탐색에서 스캔 시작점을 찾았으면, 찾고자 하는 데이터가 더 안 나타날 때까지 인덱스 리프 블록을 수평적으로 스캔한다. (데이터를 찾는 과정)

: 인덱스 리프 블록끼리는 서로 앞뒤 블록에 대한 주소값을 갖고 있어 수평적 탐색이 가능하다. (양방향 연결 리스트)

: 수평적 탐색을 하는 이유? 조건절을 만족하는 데이터를 모두 찾기 위해서, ROWID를 얻기 위해서, 필요한 칼럼을 인덱스가 모두 갖고 있어 인덱스만 스캔하고 끝나는 경우도 있지만, 일반적으로 인덱스를 스캔하고 테이블을 액세스 할 때 ROWID가 필요하다. ?????

 

결합 인덱스

- 두 개 이상 칼럼을 결합해서 인덱스를 만들 수 있다.

- 결합 인덱스 생성시 칼럼 배치 순서가 매우 중요하다. 선택도가 낮은 칼럼을 앞쪽으로 카디널리티(중복도)가 낮은 걸 앞으로 위치해야한다.

 

Range Scan이 불가능한 경우

- 색인이 정렬되어 있더라도 가공한 값이나 중간값으로는 스캔 시작점을 찾을 수 없다.

- 색인을 사용할 수 있지만, 색인을 전체 스캔해야한다.

 

1. 인덱스 칼럼을 가공하지 않아야 인덱스를 정상적으로 사용할 수 있다. (인덱스 스캔 시작점을 찾을 수 없기 때문에) 

- ex) where substr(birthday, 5, 2) = '05' , where nvl(orderCnt, 0) < 100

2. LIKE로 중간 값을 검색할 때

- ex) where 업체명 like '%대한%'

3. OR 조건 

- ex) where (전화번호 = :tel_no OR 고객명 = :cust_nm)

하지만 위 경우 OR Expansion을 통해 Index Range Scan이 가능하다. p 88, 89

select * from 고객 where 고객명 = :cust_nm

union all

select * from 고객 where 전화번호 = :tel_no

and (고객명 <> :cust_nm or 고객명 is null)

 

use_concat 힌트를 사용해 OR Expansion을 유도했을 때의 실행 계획

select /*+ use_concat */ from 고객

where (전화번호 = :tel_no OR 고객명 = :cust_nm)

 

4. IN 조건절

where 전화번호 in ( :tel_no1, :tel_no2 )

- IN 조건은 OR 조건을 다른 방식의 뿐이기 때문에 UNION ALL 방식으로 위와 같이 작성해야 한다.

select * from 고객 where 전화번호 = :tel_no1

union all

select * from 고객 where 전화번호 = :tel_no2

- IN 조건절에 대해서는 SQL 옵티마이저가 IN-List Iterator 방식을 사용하여 IN-LIST 개수만큼 Index Range Scan을 반복한다.

 

인덱스를 사용한다 : 리브 블록에서 스캔 시작점을 찾아 거기서부터 스캔하다가 중간에 멈추는 것.

+ 다중 인덱스에서는 가장 첫 번째 조건은 인덱스 선두 칼럼이 조건절에 있어야 한다.!! 

- 인덱스 선투 칼럼이 가공되지 않은 상태로 조건절에 있어야 한다. = 무조건 가능

- 하지만 인덱스가 탔다고, 끝이 아님 .. 두번째 칼럼이 가공되었다면? 스캔 범위를 최적화할 수 없음.

 

Index Range Scan : 리프 블록에서 스캔 지점을 찾아 거기서부터 스캔하다가 중간에 멈추는 것, 리프 블록 일부만 스캔하는 것

Index Full Scan : 인덱스 칼럼을 가공해도 인덱스를 사용할 수는 있지만, 스캔 시작점을 찾을 수 없고 멈출 수도 없어 리프 블록 전체를 스캔해야만 한다. 일부가 아닌 전체를 스캔하는 경우

 

-- P 94 까지 학습

 

인덱스를 이용한 소트 연산 생략

- 인덱스가 정렬되어 있는 성질을 이용하여 옵티마이저는 ORDER BY가 있어도 정렬 연산을 따로 수행하지 않는다.

- 내림차순 (DESC) 정렬에도 인덱스를 활용할 수 있다. 인덱스 리프 블록은 양방향 연결 리스토 구조이기 때문에

- 최소값과 최댓값을 구할 때도 마찬가지 이다.

 

자동 형변환

- SELECT * FROM 고객 WHERE 생년월일 = 19980803

의 경우 옵티마이저는 테이블 전체 스캔을 선택했다.

왜냐하면 생년원일은 문자형인데, 비교값을 수자형으로 표현했기 때문에 자동으로 형변환이 이루어졌다. (TO_NUMBER("생년월일"), 인덱스 칼럼을 가공한 셈이다.)

- 오라클에서는 숫자형 vs 문자형, 숫자형이 이긴다. 따라서 문자형에 형변환이 일어난 것. (LIKE일 경우에는 문자형 기준으로 숫자형 칼럼이 변환된다.)

- 날짜형과 문자형이 만나면, 날짜형이 이긴다. 하지만 날짜 포맷을 정확히 지정해주는 코딩 습관이 필요하다.

* 자동 형변환에 의존하지 말고, 인덱스 칼럼 기준으로 반대편 칼럼 또는 값을 정확히 형변환해 주어야 한다.

연산 횟수를 줄이기 위해서 TO_CHAR, TO_DATE, TO_NUMBER 같은 형변환을 의도적으로 생략하곤 하는데, SQL 성능은 그런 데에서 결정되지 않고 블록 I/O를 줄일 수 있느냐 없느냐에서 결정된다. 애초에 형변환 함수를 생략한다고 연산 횟수가 주는 것도 아니다. 옵티마이저가 자동으로 형변환 함수를 생성한다.

 

Index Range Scan

: B+트리 인덱스의 가장 일반적이고 정상적인 형태의 액세스 방식이며, 인덱스 루트에서 리프 블록까지 수직적으로 탐색한 후에 '필요한 범위만' 스캔하는 방식

: 실행 계획 : INDEX (RANGE SCAN) ..

 

Index Full Scan

: 수직적 탐색없이 인덱스 리프 블록을 처음부터 끝까지 수평적으로 탐색하는 방식

: 실행 계획 : INDEX (FULL SCAN) ..

: 데이터 검색을 위한 최적의 인덱스가 없을 때 차선으로 선택된다. ex) 인덱스 선두 칼럼이 조건절에 없는 경우 자주 사용

: 데이터 저장공간은 '칼럼 길이 X 레코드 수'에 의해 결정되는데, 인덱스가 차지하는 면적은 테이블보다 훨씬 작다. 만약 인덱스 스캔 단계에서 대부분 레코드를 필터링하고 아주 일부만 테이블을 액세스 하는 상황이라면 인덱스를 스캔하는 것이 유리하다.

: 전체 중에 극히 일부인 데이터를 찾는 경우 유리하다. 그렇지 않으면 인덱스 풀 스캔을 하더라도 거의 모든 레코드에 대해 테이블 액세스가 발생하기 때문에 Table Full Scan 보다 오히려 불리하다.

: 수행빈도가 낮은 SQL이면 상관없지만, 그렇지 않다면 칼럼이 선두인 인덱스를 생성하는 것을 추천한다.

 

Index Unique Scan

: 수직적 탐색만으로 데이터를 찾는 스캔 방식으로서, Unique 인덱스를 '=' 조건으로 탐색하는 경우 작동

: Unique 인덱스가 존재하는 칼럼은 중복 값이 입력되지 않게, DBMS가 데이터 정합성을 관리하기 때문에 데이터를 한건 찾으면 더 이상 탐색할 필요가 없다.

 

Index Skip Scan

: 조건절에 빠진 인덱스 선투 칼럼의 Distinct Value 개수가 적고 후행 칼럼의 Distinct Value 개수가 많을 때 유용하다.

: 루트 또는 브랜치 블록에서 읽은 칼럼 값 정보를 이용해 조건절에서 부합하는 레코드를 포함할 가능성 있는 리프 블록만 골라서 액기스 하는 스캔 방식이다.

: 작동하기 위한 조건.

1. 인덱스 칼럼이 업종유형코드 + 업종코드 + 기준일자 일경우, 조건절에 업종유형코드, 기준일자로 있으면 Index Range Scan을 할 수 있지만, 업종유형코드가 같은 구간을 모두 스캔해야 한다. 하지만 Index Skip Scan을 사용한다면 업종유형코드 구간에서 기준일이 포함할 가능성이 있는 리프 블록만 골라서 액세스 가능하다.

2. Distinct Value 가 적은 두 개의 선두 칼럼이 모두 조건절에 없는 경우도 유용하게 사용된다.

3. 선두 칼럼이 부등호, Between, LIKE 같은 범위검색 조건일 때도 사용한다.

어렵다 ...

 

Index Fast Full Scan

: 말그대로 Index Full Scan 보다 빠르다.

: 그 이유는 논리적인 인덱스 트리 구조를 무시하고 인덱스 세그먼트 전체를 Muiltiblock I/O 방식으로 스캔하기 때문이다.

: 관련 힌트는 index_ffs와 no_index_ffs 이다.

: 물리적으로 디스크에 저장된 순서대로 인덱스 리프 블록들을 읽어드린다.

: Multiblock I/O 방식을 사용하므로 디스크로부터 대량의 인덱스 블록을 읽어야 할 때 큰 효과를 발휘하지만, 속도는 빠르지만, 인덱스 리프 노드가 갖는 연결 리스트 구조를 무시한 채 데이터를 읽기 때문에 결과집합이 인덱스 키 순서대로 정렬되지 않는다.

: 쿼리에 사용한 칼럼이 모두 인덱스에 포함돼 있을 때만 사용할 수 있다는 점을 기억해야 한다.

: 인덱스가 파티션 돼 있지 않더라도 병렬 쿼리가 가능한 것도 중요한 특징이다. 병렬 쿼리시에는 Direct Path I/O 방식을 사용하기 때문에 I/O 속도가 더 빨라진다. ???

 

Index Range Scan Descending 

: Index Range Scan과 기본적으로 동일한 스캔 방식이다. 인덱스를 뒤에서부터 앞쪽으로 스캔하기 때문에 내림차순으로 정렬된 결과집합을 얻는다는 점만 다르다.

: 내림차순 정렬하고자할 때 옵티마이저가 알아서 인덱스를 거꾸로 읽는 실행계획을 수립하고, 그렇지 않는다면 index_desc 힌트를 이용해 유도할 수 있다.

: MAX 값을 구하고자할 떄도 자동으로 이루어진다.