Skip to content
> giho's blog
Go back

~/posts/index-scan-process

MySQL 인덱스 스캔 과정 — B-Tree 탐색부터 커버링 인덱스까지

· 3 min read
-- contents

“인덱스를 탄다”는 게 내부적으로 무슨 일인지 정리했다. MySQL은 SQL 엔진(옵티마이저·실행기) 과 스토리지 엔진(InnoDB) 이 핸들러 API로 통신하는 구조라, 인덱스 스캔도 이 둘의 협업으로 일어난다.

큰 그림: 두 계층

스캔 과정

1. 접근 방식 결정 (옵티마이저)

옵티마이저가 WHERE·JOIN·ORDER BY를 보고 어떤 인덱스로 어떤 범위를 읽을지 정한다. 결과는 EXPLAIN의 type으로 드러난다(아래).

2. B-Tree 탐색 — 시작점 찾기

InnoDB 인덱스는 B-Tree다. 루트 → 브랜치 → 리프로 내려가며 조건에 맞는 시작 위치를 찾는다. 리프 노드는 서로 연결(doubly-linked)돼 있어, 범위 스캔은 시작 리프부터 옆으로 순회한다.

3. 세컨더리 인덱스 → 클러스터드 인덱스 룩업

InnoDB에서 클러스터드 인덱스(=PK)의 리프에는 행 전체가 들어 있고, 세컨더리 인덱스의 리프에는 PK 값만 들어 있다. 그래서 세컨더리 인덱스로 찾은 뒤 SELECT에 다른 컬럼이 필요하면, 그 PK로 클러스터드 인덱스를 한 번 더 조회한다.

Note

이 2차 조회가 랜덤 I/O를 유발한다. 조회 건수가 많으면 “인덱스를 탔는데도 느린” 원인이 되곤 한다.

4. 조건 필터링 & 결과 반환

읽어온 레코드에 남은 조건을 적용하고(인덱스로 못 거른 조건은 여기서), SELECT 컬럼을 모아 결과를 반환한다. 정렬·집계가 필요하면 임시 테이블/정렬 버퍼를 거친다.

EXPLAIN 접근 타입 (좋음 → 나쁨)

옵티마이저가 고른 방식은 EXPLAIN의 type으로 확인한다.

type의미
const / systemPK·유니크로 단일 행. 가장 빠름
eq_ref조인에서 상대 테이블을 PK·유니크로 1건씩
ref비유니크 인덱스로 같은 값 여러 건
range인덱스 범위 스캔(BETWEEN, >, IN 등)
index인덱스 풀 스캔 — 인덱스 전체를 훑음(테이블 풀스캔보단 나음)
ALL테이블 풀 스캔 — 인덱스 못 탐. 튜닝 대상

커버링 인덱스

SELECT·WHERE에 필요한 모든 컬럼이 인덱스에 있으면, 3단계의 클러스터드 인덱스 재조회가 사라진다. 인덱스만 읽고 끝나는 것이다. EXPLAIN의 Extra에 Using index 로 표시된다.

-- (user_id, status) 복합 인덱스가 있을 때
SELECT status FROM orders WHERE user_id = 100;   -- 인덱스만으로 해결 → Using index
SELECT * FROM orders WHERE user_id = 100;        -- 다른 컬럼 필요 → PK로 클러스터드 재조회

정리



-- related


Next Post
JSON column type
giho=#searchesc