데이터베이스 연구실.log

인덱스 스캔 방식

인덱스 스캔 유형별 사용 조건과 힌트, 실행계획 및 버퍼 수 비교

Index Range ScanIndex Unique ScanIndex Full ScanIndex Fast Full ScanIndex Skip ScanIndex Range Scan DescendingMIN/MAXindex_ssindex_ffs

#핵심 요약

스캔 방식 탐색 조건과 블록 읽기 방식
Index Unique Scan Unique 인덱스의 모든 컬럼을 = 조건으로 찾을 때
수직적 탐색만 하고 멈춤
Index Range Scan 인덱스 선두 컬럼으로 시작점을 찾은 뒤 필요한 범위만 수평 탐색
Index Full Scan 리프 블록 가장 왼쪽부터 끝까지 수평 탐색
결과가 인덱스 순서로 정렬됨
한 블록씩 읽음
Index Fast Full Scan 인덱스 블록을 물리적 순서대로 읽음
Multiblock I/O와 병렬 처리가 가능하지만 정렬 순서는 보장하지 않음
필요한 컬럼이 모두 인덱스에 있어야 함
Index Skip Scan 결합 인덱스 선두 컬럼이 조건에 없을 때, 브랜치 블록 정보로 조건에 맞을 수 있는 리프만 골라 읽음
선두 컬럼의 값 종류가 적을 때 유리함
Index Range Scan Descending 인덱스를 뒤에서부터 읽어 내림차순 결과를 정렬 없이 냄

공식 문서(SQL Tuning Guide 19c, Optimizer Access Paths)는 결합 인덱스 선두 컬럼이 조건에 없을 때 인덱스를 고르지 않을 수 있지만 Index Skip Scan을 고를 수 있다고 적음
각 스캔의 공식 정의 문장은 확인 불가

#필수 명령어 또는 SQL

#Index Unique Scan과 Range Scan

SQL*Plus (실습 계정)객체 또는 설정을 변경함
CREATE INDEX emp_empno ON emp (empno);          -- non unique
SQL*Plus (실습 계정)조회만 함
SELECT empno, ename, sal
  FROM emp
 WHERE empno = 7788;

결과 해석:

실습 결과: INDEX RANGE SCAN(Buffers 2) 위에 TABLE ACCESS BY INDEX ROWID BATCHED, 합계 Buffers 3

SQL*Plus (실습 계정)데이터 또는 파일을 삭제함, 되돌릴 수 없음
DROP INDEX emp_empno;
CREATE UNIQUE INDEX emp_empno ON emp (empno);

결과 해석:

실습 결과: 같은 조건으로 SELECT *를 하면 INDEX UNIQUE SCAN(Buffers 1)과 TABLE ACCESS BY INDEX ROWID, 합계 Buffers 2
중복이 있는 job에 Unique 인덱스를 만들면 ORA-01452가 남

퀴즈 답: PK가 주문일자 + 주문번호인데 주문번호만 조건으로 주면 같은 주문번호가 여러 날짜에 있을 수 있어 한 건을 보장할 수 없으므로 Range Scan이 됨

#Index Full Scan

SQL*Plus (실습 계정)객체 또는 설정을 변경함
CREATE INDEX emp_job ON emp (job);
ALTER TABLE emp MODIFY job CONSTRAINT emp_job_nn NOT NULL;
SQL*Plus (실습 계정)조회만 함
SELECT job, COUNT(*)
  FROM emp
 GROUP BY job;

결과 해석:

실습 기록: 인덱스만 만든 상태에서는 여전히 TABLE ACCESS FULL이었고, 실습에서는 이어서 job에 NOT NULL 제약을 걸어 Index Full Scan을 확인하게 했음
공식 문서도 인덱스는 NULL 항목을 담지 않으므로 인덱스 컬럼에 NULL이 있으면 행 수를 세는 데 인덱스를 쓸 수 없다고 적음(Full Table Scan과 인덱스 접근)
실습 다른 답은 WHERE deptno IS NOT NULL을 붙이고 index 힌트를 줬으며 "Index Full Scan은 정렬된 결과를 보장해 SORT ORDER BY가 나오지 않는다"고 설명했음

실습 대용량 실습(10만 건 emp, emp(ename, sal) 인덱스, ORDER BY ename):

조건 Table Full Scan Index Full Scan
sal > 9000(6000건) Buffers 665 + SORT ORDER BY Buffers 1129
sal > 1000(95000건), first_rows 힌트 Buffers 665 + SORT ORDER BY Buffers 2903

실습 결론: Index Full Scan은 한 블록씩 읽어 블록 양이 많아지면 Table Full Scan보다 크게 느려질 수 있음
주석의 분포 목표(SAL > 9000 약 1%, 6%)와 결과 6000건이 서로 맞지 않음

#Index Fast Full Scan

SQL*Plus (실습 계정)조회만 함
SELECT /*+ index_ffs(emp emp_job) */ job, COUNT(*)
  FROM emp
 WHERE job IS NOT NULL
 GROUP BY job;

결과 해석:

설명: Index Full Scan보다 빠른 이유는 Multiblock I/O와 병렬 처리 두 가지임
실습에서는 둘을 살린 예로 다음을 실행했음

SQL*Plus (실습 계정)현재 세션 설정만 변경함
ALTER SESSION SET db_file_multiblock_read_count = 512;
SQL*Plus (실습 계정)조회만 함
SELECT /*+ index_ffs(emp emp_job) parallel_index(emp, emp_job, 4) */ job, COUNT(*)
  FROM emp
 WHERE job IS NOT NULL
 GROUP BY job;

결과 해석:

db_file_multiblock_read_count 512가 실제로 적용되는 상한은 기재 없음

#Index Skip Scan

SQL*Plus (실습 계정)객체 또는 설정을 변경함
CREATE INDEX emp_deptno_job ON emp (deptno, job);
SQL*Plus (실습 계정)조회만 함
SELECT /*+ full(emp) */ * FROM emp WHERE job = 'MANAGER';
SELECT /*+ index_ss(emp emp_deptno_job) */ * FROM emp WHERE job = 'MANAGER';

결과 해석:

실습 실무 문제(120만 건 mcustsum, 인덱스 salemm + salegb):

SQL*Plus (실습 계정)조회만 함
SELECT /*+ full(t) */ COUNT(*)
  FROM mcustsum t
 WHERE salegb = 'A';                         -- 실습 기록 3835

SELECT /*+ index_ss(t m_salemm_salegb) */ COUNT(*)
  FROM mcustsum t
 WHERE salegb = 'A';                         -- 실습 기록 302

결과 해석: 선두 컬럼 salemm의 값이 12가지뿐이라 스킵 스캔이 효과적임(퀴즈의 "선두 컬럼 종류가 적어야 한다"와 같은 원리)
mcustsum 생성 스크립트는 스캔 범위와 필터 조건에 있음
반대로 막는 힌트는 NO_INDEX_SS임

#Index Range Scan Descending과 MIN/MAX

SQL*Plus (실습 계정)조회만 함
SELECT *
  FROM emp
 WHERE empno > 0
 ORDER BY empno DESC;

결과 해석:

설명: empno에 PK가 있으면 INDEX RANGE SCAN DESCENDING으로 가장 큰 값부터 리프를 오른쪽에서 왼쪽으로 읽어 정렬을 생략함
MAX 함수로 가장 큰 값 하나만 구할 때도 인덱스 뒤에서 한 건만 읽고 멈춤(FIRST ROW, INDEX RANGE SCAN (MIN/MAX))
실습 예:

SQL*Plus (실습 계정)객체 또는 설정을 변경함
CREATE INDEX emp_x02 ON emp (deptno, sal);
SQL*Plus (실습 계정)조회만 함
SELECT deptno, dname, loc,
       (SELECT /*+ no_unnest */ MAX(sal) FROM emp WHERE deptno = d.deptno)
  FROM dept d;

결과 해석:

MIN/MAX 최적화가 깨지는 경우는 인덱스 사용을 방해하는 조건에서 다룸

SEO_HKabsumveritas@gmail.comBuilt with Claude Code & Codex

마지막 갱신 개인 학습 기록이며 운영 환경 적용 전 공식 문서 확인 필요

두 글자 이상 입력, 위아래 화살표로 이동하고 Enter로 열기