스택큐힙리스트

Index Skip Scan 완전 정복: 선두 칼럼이 없어도 인덱스를 타는 마법 본문

개발

Index Skip Scan 완전 정복: 선두 칼럼이 없어도 인덱스를 타는 마법

스택큐힙리스트 2025. 7. 11. 22:14
반응형

Index Skip Scan이란?

B-Tree 인덱스는 보통 왼쪽(선두) 칼럼으로 정렬돼 있어, 그 칼럼이 조건에 없으면 인덱스를 통째로 훑는(Full Scan) 일이 많습니다.
Skip Scan은 선두 칼럼의 각 값(또는 범위)을 “건너뛰며” 하위 칼럼을 탐색해 “마치 여러 단일-칼럼 인덱스가 있는 것처럼” 동작합니다. Oracle·MySQL·TiDB 등은 옵티마이저가 스스로 판단해 적용하며, 실행계획에 INDEX SKIP SCAN(Oracle)·Using index for skip scan(MySQL EXPLAIN Extra) 등이 찍힙니다.


동작 원리 깊이 보기

  1. 선두 칼럼 Distinct 스캔
    • 예) (status, created_at) 인덱스에서 status(A/B/C) 값별 루트→리프 경로를 순차로 탐색
  2. 하위 칼럼 Range 검색
    • 각 status 경로 안에서 created_at ≥ 2025-01-01 조건으로 범위 스캔
  3. 필요 없는 리프 블록은 건너뜀(skip)
    • B, C path의 리프를 재사용하므로 “다중 인덱스” 효과

골든 룰
선두 칼럼 카디널리티가 낮아야(값 종류가 적어야) skip step이 적고 이득이 큽니다.


어떤 DB에서 지원하나?


 

제품 지원 현황 힌트/옵션
Oracle 9i+

기본 지원, 실행계획 OPERATION 열 INDEX SKIP SCAN 힌트 INDEX_SS()
MySQL 5.7+ (InnoDB) Extra = Using index for skip scan 옵티마이저 힌트 SKIP_SCAN / NO_SKIP_SCAN
PostgreSQL 17·18 버전에서 패치 진행 중, 아직 메인라인 포함 전(2025-07 기준)  
TiDB 6.0+ 기본 지원  
SQL Server / SQLite 공식 지원 없음  
 

 


언제 사용하면 좋을까?

  • 선두 칼럼이 로우 상태값, 타입 코드 등 저카디널리티
  • 하위 칼럼에 범위/정렬 조건만 존재할 때
-- status 조건 없이 created_at 범위 + 정렬
SELECT * FROM orders
 WHERE created_at >= '2025-01-01'
 ORDER BY created_at, id
 LIMIT 50;
  • 읽기 전용·OLAP형 조회: 테이블 업데이트가 많지 않을수록 효과 극대화

성능 비교 예시(MySQL 8.4, 1 M rows)


 

패턴 p95 지연 읽은 Rows
Offset 100 k + Limit 50 220 ms 100 050
Skip Scan(covering X) 9 ms 50
Skip Scan + Covering Index 4 ms 50
 

Covering Index까지 결합하면 테이블 접근이 사라져 I/O가 절반 이하로 감소합니다.


옵티마이저가 Skip Scan을 고르지 않을 때

  1. 힌트 강제
SELECT /*+ SKIP_SCAN(t1) */ … FROM t1 …  -- MySQL
SELECT /*+ INDEX_SS(orders idx_status_created) */ …  -- Oracle
  1. 통계 갱신 후 재시도
    • 잘못된 NDV(고유값 수) 추정이 선택을 막을 수 있음.
  2. 인덱스 재설계
    • 아예 (created_at, id) 단독 인덱스를 두는 편이 더 빠른 상황도 있음.

한계와 주의점

  • 선두 칼럼이 고카디널리티(고유값 많음) → skip 횟수↑, 이득↓
  • 높은 업데이트 빈도 → 인덱스 블록 분할 심화, Fragmentation 발생
  • Oracle은 함수-기반·언어별 정렬 인덱스에서는 Skip Scan 불가 support.oracle.com
  • 실행계획이 INDEX RANGE SCAN과 혼합될 때 성능이 오히려 떨어질 수 있으므로 벤치마크 필수.

실전 체크리스트

질문 체크 포인트
선두 칼럼 NDV가 충분히 낮은가? SELECT COUNT(DISTINCT col1)/COUNT(*)
통계 최신화돼 있는가? ANALYZE TABLE / DBMS_STATS.GATHER_*
Covering 가능한가? SELECT 칼럼을 인덱스에 다 포함
실행계획에서 Skip Scan 확인? Oracle PLAN OP, MySQL Extra
힌트·파라미터? SKIP_SCAN, index_ss_dj_enabled=true(Oracle)
반응형
Comments