Devin.KR

인덱스 스캔 방식 - Range·Unique·Full·Fast Full·Skip Scan

개발자KR 조회 9

이 장에서 배우는 것

앞 장에서는 B*Tree 인덱스의 구조와 클러스터링 팩터가 인덱스 사용 여부에 미치는 영향을 다뤘다. 인덱스가 있다고 해서 옵티마이저가 항상 같은 방식으로 그 인덱스를 읽지는 않는다. 조건의 모양과 데이터 분포에 따라 오라클은 다섯 가지 인덱스 스캔 중 하나를 고른다. 이 장에서는 각 스캔이 실행계획에 어떻게 표기되고, 언제 선택되며, 어떤 조건에서는 인덱스 자체를 쓸 수 없게 되는지를 다룬다.

  • INDEX UNIQUE SCAN·RANGE SCAN·FULL SCAN·FAST FULL SCAN·SKIP SCAN 다섯 가지 실행계획 표기를 구분할 수 있다
  • 각 스캔이 리프 블록을 얼마나, 어떤 순서로 읽는지 설명할 수 있다
  • 컬럼 가공·형변환·부정 조건·LIKE 앞 % 조건이 인덱스를 못 쓰게 만드는 이유를 설명할 수 있다
  • 실행계획에서 원하지 않는 스캔이 나왔을 때 조건문을 고쳐 쓸 수 있다

문제 상황

온라인 서점의 주문 테이블은 1천만 건 규모다. 마이페이지의 "주문내역" 화면은 회원번호로 필터링해 대부분 빠르게 응답하지만, 몇 가지 조건이 붙으면 갑자기 느려진다는 문의가 들어왔다. 개발자가 실행계획을 확인해 보니 세 가지 증상이 겹쳐 있었다.

첫째, "최근 주문만 보기" 화면에서 TRUNC(주문일시) = ... 조건을 쓴 화면은 INDEX RANGE SCAN이 아니라 TABLE ACCESS FULL로 나왔다. 둘째, "취소 제외" 화면에서 주문상태코드 <> '09' 조건을 쓴 화면도 마찬가지였다. 셋째, 회원 등급별 관리자 화면에서는 예상과 다르게 INDEX SKIP SCAN이 떴는데, 담당자는 이 실행계획 표기를 처음 봐서 정상인지 판단하지 못했다.

세 증상 모두 원인이 다르다. 인덱스 스캔 방식을 구분해서 읽을 수 있어야 어느 것이 정상이고 어느 것이 조건문을 고쳐야 하는 경우인지 판단할 수 있다.

다섯 가지 스캔 방식과 실행계획 표기

같은 인덱스라도 조건의 모양에 따라 오라클은 인덱스 리프 블록을 다르게 읽는다. 아래 그림은 8개 리프 블록을 예로 들어 네 가지 접근 범위를 비교한 것이다. 스킵 스캔은 다음 절에서 별도로 다룬다.

Unique·Range 스캔은 리프 일부만 읽고, Full 스캔은 순서대로, Fast Full 스캔은 저장 순서대로 전체를 읽는다

INDEX UNIQUE SCAN

유일 인덱스(기본키·유일키)의 모든 구성 컬럼에 등호 조건이 걸릴 때 나온다. 결과가 최대 1건이라는 것이 보장되므로 리프 블록 한 곳만 확인하고 끝난다. 도서.isbn처럼 업무상 유일한 값을 등호로 조회하는 경우가 대표적이다.

INDEX RANGE SCAN

가장 흔한 스캔이다. 인덱스 선두 컬럼에 등호나 범위(>=, BETWEEN, LIKE '문자열%') 조건이 걸리면, 조건을 만족하는 리프 블록 구간만 이어서 읽는다. 유일 인덱스라도 등호가 아닌 범위 조건이면 결과가 여러 건일 수 있으므로 RANGE SCAN으로 나온다.

INDEX FULL SCAN

리프 블록을 처음부터 끝까지 정렬 순서대로 읽는다. 조건이 인덱스 선두 컬럼을 좁히지 못하거나 없는데, ORDER BY나 GROUP BY가 인덱스 컬럼 순서와 일치해서 정렬(SORT) 연산을 피할 수 있을 때 옵티마이저가 선택한다. 순서를 보장한다는 점이 다음의 FAST FULL SCAN과 가장 큰 차이다.

INDEX FAST FULL SCAN

인덱스 세그먼트를 테이블처럼 처음부터 끝까지 읽되, 리프 블록 사이의 정렬 순서를 지키지 않는다. 대신 여러 블록을 한 번에 읽는 멀티블록 I/O를 쓸 수 있어 같은 "전체를 읽는" 작업이라도 FULL SCAN보다 빠른 경우가 많다. 조회할 컬럼이 모두 인덱스에 들어 있는 커버링 인덱스 상황에서, 정렬이 필요 없는 집계 쿼리에 주로 나온다. 순서를 보장하지 않으므로 ORDER BY를 대체할 수는 없다.

INDEX SKIP SCAN

결합 인덱스에서 선두 컬럼 조건이 없더라도, 그 선두 컬럼의 distinct 값 개수(카디널리티)가 적으면 오라클은 선두 값별로 인덱스를 나눠 각각 범위 스캔을 반복한다. 이것이 스킵 스캔이다. 아래 그림처럼 (등급코드, 가입일자) 결합 인덱스에서 가입일자 조건만 있어도, 등급코드 값이 3개뿐이면 3번의 부분 범위 스캔으로 처리할 수 있다.

스킵 스캔은 선두 컬럼의 distinct 값마다 인덱스를 나눠 부분 범위 스캔을 반복한다

선두 컬럼의 distinct 값이 3개면 3번만 나누면 되지만, 50개면 50번을 나눠야 한다. 이 경우는 오히려 INDEX FULL SCAN보다 비효율적일 수 있어, 옵티마이저는 선두 컬럼 카디널리티가 낮을 때만 스킵 스캔을 고른다. 스킵 스캔이 보인다는 것은 대개 "결합 인덱스 순서를 조회 조건과 다시 맞춰야 한다"는 신호로 읽으면 된다. 결합 인덱스 컬럼 순서를 정하는 기준은 다음 장에서 다룬다.

인덱스를 쓰지 못하게 만드는 조건

인덱스가 있고 조건에 그 컬럼이 들어 있어도, 조건의 형태 때문에 옵티마이저가 인덱스를 후보에서 제외하는 경우가 있다. 공통적인 이유는 하나다. 인덱스 리프 블록에는 컬럼의 원본 값이 정렬되어 저장되는데, 조건이 그 원본 값을 그대로 비교하지 않으면 어디서부터 어디까지 읽어야 할지 경계를 정할 수 없다는 것이다.

인덱스 사용을 막는 대표 조건과 대안
조건 유형예시인덱스 사용 가능 여부대안
컬럼 가공TRUNC(주문일시)='...'불가(함수 기반 인덱스 없으면)범위 조건으로 재작성
암시적 형변환주문상태코드=1불가리터럴을 컬럼 타입에 맞춤
부정 조건주문상태코드<>'01'대체로 불가IN 목록으로 긍정 조건 전환
LIKE 앞 %제목 LIKE '%SQL%'불가앞부분 고정 검색 또는 전문 검색

컬럼 가공과 형변환은 결국 같은 문제다. 컬럼에 함수나 연산을 걸든, 오라클이 타입을 맞추려고 내부적으로 함수를 씌우든, 인덱스에 저장된 원본 값과 조건 값을 직접 비교할 수 없게 되는 것은 같다. 부정 조건은 "이 값이 아닌 나머지 전부"를 뜻하므로 인덱스로 좁힐 수 있는 연속 구간이 정의되지 않는다. LIKE 앞 %는 인덱스가 문자열을 앞에서부터 정렬해 두기 때문에, 앞자리가 고정되지 않으면 탐색을 시작할 위치조차 정할 수 없다.

완성 코드

-- 1. 테이블 생성
CREATE TABLE 회원 (
  회원번호     NUMBER        PRIMARY KEY,
  이메일       VARCHAR2(100) NOT NULL,
  가입일자     DATE          NOT NULL,
  등급코드     CHAR(2)       NOT NULL
);

CREATE TABLE 도서 (
  도서번호     NUMBER        PRIMARY KEY,
  isbn         VARCHAR2(20)  NOT NULL,
  제목         VARCHAR2(200) NOT NULL,
  출판사번호   NUMBER,
  정가         NUMBER,
  출간일자     DATE
);

CREATE TABLE 주문 (
  주문번호     NUMBER   PRIMARY KEY,
  회원번호     NUMBER   NOT NULL,
  주문일시     DATE     NOT NULL,
  주문상태코드 CHAR(2)  NOT NULL,
  총결제금액   NUMBER
);
-- 1천만 건 규모를 가정한다

CREATE TABLE 주문상세 (
  주문번호     NUMBER NOT NULL,
  주문상세번호 NUMBER NOT NULL,
  도서번호     NUMBER NOT NULL,
  수량         NUMBER,
  판매가       NUMBER,
  CONSTRAINT pk_주문상세 PRIMARY KEY (주문번호, 주문상세번호)
);

CREATE TABLE 리뷰 (
  리뷰번호     NUMBER PRIMARY KEY,
  도서번호     NUMBER NOT NULL,
  회원번호     NUMBER NOT NULL,
  평점         NUMBER,
  작성일자     DATE
);

-- 2. 인덱스 생성
CREATE UNIQUE INDEX ux_도서_isbn      ON 도서(isbn);
CREATE INDEX ix_주문_회원일시         ON 주문(회원번호, 주문일시);
CREATE INDEX ix_주문상세_도서         ON 주문상세(도서번호, 판매가);
CREATE INDEX ix_회원_등급가입         ON 회원(등급코드, 가입일자);

-- 3. 시연용 샘플 데이터
INSERT INTO 회원 VALUES (1001, 'reader01@mail.com', DATE '2023-03-10', '01');
INSERT INTO 회원 VALUES (1002, 'reader02@mail.com', DATE '2024-01-15', '02');
INSERT INTO 회원 VALUES (1003, 'reader03@mail.com', DATE '2024-02-20', '01');
INSERT INTO 회원 VALUES (1004, 'reader04@mail.com', DATE '2024-05-02', '03');

INSERT INTO 도서 VALUES (5001, '9791100000012', 'SQL 튜닝 첫걸음', 30, 22000, DATE '2022-03-01');
INSERT INTO 도서 VALUES (5002, '9791100000029', '오라클 실행계획 읽기', 30, 26000, DATE '2023-07-10');
INSERT INTO 도서 VALUES (5003, '9791100000036', '인덱스 설계 노트', 31, 19000, DATE '2024-01-20');

INSERT INTO 주문 VALUES (900001, 1001, TO_DATE('2024-05-01 10:20:00','YYYY-MM-DD HH24:MI:SS'), '01', 22000);
INSERT INTO 주문 VALUES (900002, 1001, TO_DATE('2024-06-11 09:05:00','YYYY-MM-DD HH24:MI:SS'), '02', 26000);
INSERT INTO 주문 VALUES (900003, 1002, TO_DATE('2024-06-15 14:40:00','YYYY-MM-DD HH24:MI:SS'), '01', 19000);
INSERT INTO 주문 VALUES (900004, 1003, TO_DATE('2024-07-02 11:10:00','YYYY-MM-DD HH24:MI:SS'), '09', 22000);
INSERT INTO 주문 VALUES (900005, 1004, TO_DATE('2024-07-20 16:30:00','YYYY-MM-DD HH24:MI:SS'), '01', 26000);

INSERT INTO 주문상세 VALUES (900001, 1, 5001, 1, 22000);
INSERT INTO 주문상세 VALUES (900002, 1, 5002, 1, 26000);
INSERT INTO 주문상세 VALUES (900003, 1, 5003, 1, 19000);
INSERT INTO 주문상세 VALUES (900004, 1, 5001, 1, 22000);
INSERT INTO 주문상세 VALUES (900005, 1, 5002, 1, 26000);

COMMIT;

-- 4. 스캔별 실행계획 확인
-- Q1: INDEX UNIQUE SCAN
EXPLAIN PLAN FOR
SELECT isbn, 제목
FROM   도서
WHERE  isbn = '9791100000012';

SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);

-- Q2: INDEX RANGE SCAN
EXPLAIN PLAN FOR
SELECT 주문번호, 주문일시, 총결제금액
FROM   주문
WHERE  회원번호 = 1001
AND    주문일시 >= TO_DATE('2024-01-01','YYYY-MM-DD');

SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);

-- Q3: INDEX FULL SCAN (정렬 회피)
EXPLAIN PLAN FOR
SELECT 회원번호, 주문일시
FROM   주문
ORDER BY 회원번호, 주문일시;

SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);

-- Q4: INDEX FAST FULL SCAN (커버링 집계)
EXPLAIN PLAN FOR
SELECT COUNT(*), AVG(판매가)
FROM   주문상세;

SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);

-- Q5: INDEX SKIP SCAN
EXPLAIN PLAN FOR
SELECT 회원번호, 이메일
FROM   회원
WHERE  가입일자 >= DATE '2024-01-01';

SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);

줄별 해설

  • CREATE TABLE 주문 문장의 주문일시, 주문상태코드는 이 장의 조건절 예시에서 계속 등장하는 컬럼이다. 상태코드는 CHAR(2)로 두어 뒤에서 형변환 문제를 실제로 재현한다.
  • ux_도서_isbn은 유일 인덱스다. Q1처럼 전체 키에 등호를 걸면 UNIQUE SCAN이 나온다.
  • ix_주문_회원일시(회원번호, 주문일시)는 이 장의 RANGE SCAN(Q2)과 FULL SCAN(Q3) 예시에 동시에 쓰인다. 조건 모양에 따라 같은 인덱스도 다르게 읽힌다는 것을 보여준다.
  • ix_주문상세_도서(도서번호, 판매가)는 Q4에서 COUNT(*)와 AVG(판매가) 계산에 필요한 값을 모두 담고 있어, 테이블을 건드리지 않고 인덱스만으로 답을 낼 수 있는 커버링 인덱스 역할을 한다.
  • ix_회원_등급가입(등급코드, 가입일자)은 선두 컬럼인 등급코드의 distinct 값이 적다(샘플에서는 '01','02','03' 세 종류). Q5처럼 가입일자만으로 조회하면 스킵 스캔의 조건이 갖춰진다.
  • Q3의 ORDER BY 회원번호, 주문일시는 인덱스 컬럼 순서와 정확히 같다. 옵티마이저는 정렬 연산을 새로 하는 대신 인덱스를 순서대로 읽는 쪽을 택한다.
  • Q4에는 WHERE절이 없다. 조건으로 좁힐 범위가 없고 순서도 필요 없으니, 인덱스를 통째로 빠르게 읽는 FAST FULL SCAN이 유리하다.

실행 결과

-- Q1 실행계획 (Unique Scan)
--------------------------------------------------------
| Id | Operation                   | Name          | Rows | Cost |
--------------------------------------------------------
|  0 | SELECT STATEMENT             |               |    1 |    2 |
|  1 |  TABLE ACCESS BY INDEX ROWID | 도서          |    1 |    2 |
|* 2 |   INDEX UNIQUE SCAN          | UX_도서_ISBN  |    1 |    1 |
--------------------------------------------------------

Predicate Information (identified by operation id):
   2 - access("ISBN"='9791100000012')
-- Q2 실행계획 (Range Scan)
--------------------------------------------------------------
| Id | Operation                   | Name               | Rows | Cost |
--------------------------------------------------------------
|  0 | SELECT STATEMENT             |                     |   18 |    5 |
|  1 |  TABLE ACCESS BY INDEX ROWID | 주문                |   18 |    5 |
|* 2 |   INDEX RANGE SCAN           | IX_주문_회원일시    |   18 |    3 |
--------------------------------------------------------------

Predicate Information (identified by operation id):
   2 - access("회원번호"=1001 AND "주문일시">=TO_DATE('2024-01-01','YYYY-MM-DD'))
-- Q3 실행계획 (Full Scan)
------------------------------------------------------------------
| Id | Operation      | Name               | Rows     | Cost  |
------------------------------------------------------------------
|  0 | SELECT STATEMENT|                    | 10000000 | 41823 |
|  1 |  INDEX FULL SCAN | IX_주문_회원일시   | 10000000 | 41823 |
------------------------------------------------------------------
-- Q4 실행계획 (Fast Full Scan)
------------------------------------------------------------------
| Id | Operation           | Name               | Rows     | Cost  |
------------------------------------------------------------------
|  0 | SELECT STATEMENT     |                    |        1 | 21406 |
|  1 |  SORT AGGREGATE      |                    |        1 |       |
|  2 |   INDEX FAST FULL SCAN| IX_주문상세_도서   | 30000000 | 21406 |
------------------------------------------------------------------
-- Q5 실행계획 (Skip Scan)
--------------------------------------------------------------
| Id | Operation                   | Name               | Rows   | Cost |
--------------------------------------------------------------
|  0 | SELECT STATEMENT             |                     | 412000 |  874 |
|  1 |  TABLE ACCESS BY INDEX ROWID | 회원                | 412000 |  874 |
|* 2 |   INDEX SKIP SCAN            | IX_회원_등급가입    | 412000 |  762 |
--------------------------------------------------------------

Predicate Information (identified by operation id):
   2 - access("가입일자">=DATE '2024-01-01')
       filter("가입일자">=DATE '2024-01-01')

Q3와 Q4 모두 "전체를 읽는다"는 결과는 같지만 Rows 추정치와 연산이 다르다. Q3는 INDEX FULL SCAN 한 단계로 정렬된 순서를 유지하고, Q4는 SORT AGGREGATE 아래에서 INDEX FAST FULL SCAN이 순서 없이 인덱스를 읽은 뒤 집계만 수행한다.

실무에서 자주 틀리는 것

날짜 컬럼에 함수를 씌운다

"오늘 주문만 보기" 화면에서 흔히 나오는 실수다. TRUNC를 걸면 인덱스 컬럼의 원본 값과 비교할 수 없어 RANGE SCAN이 FULL SCAN으로 바뀐다.

컬럼 가공은 인덱스 경계를 무너뜨리지만 범위 조건으로 바꾸면 range scan이 유지된다
-- 잘못된 조건: 컬럼을 가공해 인덱스를 못 쓴다
SELECT 주문번호, 총결제금액
FROM   주문
WHERE  TRUNC(주문일시) = TO_DATE('2024-05-01','YYYY-MM-DD');

-- 고친 조건: 원본 컬럼을 그대로 비교한다
SELECT 주문번호, 총결제금액
FROM   주문
WHERE  주문일시 >= TO_DATE('2024-05-01','YYYY-MM-DD')
AND    주문일시 <  TO_DATE('2024-05-02','YYYY-MM-DD');

문자 컬럼을 숫자와 비교한다

주문상태코드는 CHAR(2)인데 숫자 리터럴과 비교하면 오라클이 컬럼 쪽에 TO_NUMBER를 암시적으로 씌운다. 결과적으로 컬럼 가공과 같은 문제가 생긴다.

-- 잘못된 조건: 암시적 형변환이 컬럼에 걸린다
SELECT * FROM 주문 WHERE 주문상태코드 = 1;

-- 고친 조건: 리터럴을 컬럼 타입에 맞춘다
SELECT * FROM 주문 WHERE 주문상태코드 = '01';

부정 조건으로 "제외"를 표현한다

<>나 NOT IN은 "이 값만 빼고 나머지 전부"를 뜻해서 인덱스로 좁힐 연속 구간이 없다. 상태값 종류가 정해져 있다면 긍정 조건인 IN 목록으로 바꿀 수 있다.

-- 잘못된 조건: 부정 조건은 범위를 좁히지 못한다
SELECT * FROM 주문 WHERE 주문상태코드 <> '09';

-- 고친 조건: 남길 값을 목록으로 나열한다
SELECT * FROM 주문 WHERE 주문상태코드 IN ('01','02');

검색어 앞뒤에 모두 %를 붙인다

제목 검색을 LIKE '%키워드%'로 구현하면 인덱스가 시작 위치를 정할 수 없어 RANGE SCAN이 불가능하다. 앞자리를 고정할 수 있는 검색이라면 뒤쪽 %만 남기고, 자유 검색이 꼭 필요하면 별도의 전문 검색 인덱스를 검토해야 한다.

-- 잘못된 조건: 앞부분 %는 인덱스 시작점을 정할 수 없다
SELECT * FROM 도서 WHERE 제목 LIKE '%튜닝%';

-- 고친 조건: 앞자리를 고정할 수 있는 경우
SELECT * FROM 도서 WHERE 제목 LIKE '오라클%';

한눈에 보기

인덱스 스캔 다섯 가지 요약
스캔 방식실행계획 표기선택되는 대표 조건리프 블록 접근 범위
Unique ScanINDEX UNIQUE SCAN유일 인덱스 전체 컬럼 등호리프 1건
Range ScanINDEX RANGE SCAN선두 컬럼 등호·범위 조건조건에 맞는 연속 구간
Full ScanINDEX FULL SCAN정렬이 필요하나 조건은 넓거나 없음전체(정렬 순서대로)
Fast Full ScanINDEX FAST FULL SCAN커버링 인덱스로 전체·집계 조회, 순서 불필요전체(저장 순서대로)
Skip ScanINDEX SKIP SCAN선두 컬럼 생략 + 선두 컬럼 저카디널리티선두 값별 구간을 나눠서
Oracle 19c와 MySQL 8 실행계획 표기 비교
스캔 개념Oracle 19c 표기MySQL 8 표기비고
UniqueINDEX UNIQUE SCANtype=const/eq_ref유일 인덱스+등호일 때
RangeINDEX RANGE SCANtype=range/ref범위·등호 조건
Full(정렬 보장)INDEX FULL SCANtype=indexORDER BY 대체용
Fast Full(정렬 무시)INDEX FAST FULL SCANtype=indexMySQL은 정렬 보장 여부를 별도 표기하지 않음
SkipINDEX SKIP SCANExtra: Using index for skip scanMySQL 8.0.13부터 지원

연습 문제

  1. 결합 인덱스 (상태코드, 주문일시)에서 상태코드의 distinct 값이 3개일 때와 50개일 때, 상태코드 조건 없이 주문일시만으로 조회하면 스킵 스캔의 효율이 어떻게 달라지는지 설명하라.
  2. 다음 두 조건 중 INDEX RANGE SCAN이 가능한 것을 고르고 이유를 쓰라.
    (가) WHERE 주문상태코드 = 1 (주문상태코드는 CHAR(2))
    (나) WHERE 주문상태코드 = '01'
  3. WHERE 제목 LIKE '%튜닝%' 조건이 왜 인덱스 RANGE SCAN이 될 수 없는지 설명하고, 실무에서 흔히 쓰는 대안 한 가지를 쓰라.
  4. INDEX FULL SCAN과 INDEX FAST FULL SCAN의 차이를 정렬 순서 보장 여부 관점에서 설명하고, 각각이 유리한 상황을 예로 들라.

정답과 해설

1. distinct 값이 3개면 인덱스를 3번만 나눠서 각 구간을 부분 범위 스캔하면 되므로 효율적이다. distinct 값이 50개면 50번을 나눠야 하므로 나누는 횟수가 늘어날수록 이점이 줄고, 특정 임계값을 넘으면 INDEX FULL SCAN이나 TABLE ACCESS FULL보다 오히려 비효율적일 수 있다. 이 경우는 인덱스 컬럼 순서를 조회 조건에 맞게 바꾸는 것이 근본적인 해결책이다.

2. (나)가 가능하다. (가)는 문자형 컬럼을 숫자 리터럴과 비교해서 오라클이 컬럼에 암시적으로 TO_NUMBER를 씌운다. 컬럼이 가공되면 인덱스에 저장된 원본 값과 직접 비교할 수 없어 RANGE SCAN을 쓸 수 없다. (나)는 리터럴이 컬럼과 같은 문자형이라 원본 값을 그대로 비교하므로 인덱스를 정상적으로 쓸 수 있다.

3. 인덱스는 문자열을 앞에서부터 정렬해 저장한다. 앞부분에 %가 있으면 어떤 문자로 시작하는지 알 수 없어 탐색을 시작할 리프 블록의 위치조차 정할 수 없다. 실무에서는 앞자리를 고정할 수 있는 조건으로 바꾸거나(LIKE '문자열%'), 자유 검색이 꼭 필요하면 전문 검색 전용 인덱스를 별도로 구성한다.

4. INDEX FULL SCAN은 리프 블록을 인덱스 정렬 순서대로 읽어 결과 순서를 보장하므로, ORDER BY·GROUP BY가 인덱스 컬럼 순서와 일치할 때 정렬 연산을 피하려고 쓰인다. INDEX FAST FULL SCAN은 순서를 보장하지 않는 대신 멀티블록 I/O로 더 빠르게 전체를 읽을 수 있어, 커버링 인덱스로 집계나 카운트처럼 순서가 필요 없는 조회를 할 때 유리하다.

댓글 0

아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.

댓글을 남기려면 로그인이 필요합니다.