Study/MySQL

B-Tree 인덱스를 통한 데이터 읽기

kanado 2026. 3. 2. 18:11

인덱스 레인지 스캔

인덱스 레인지 스캔은 인덱스에서 어디부터 어디까지 읽을지 범위가 정해졌을 때 사용하는 방식.

예시) WHERE name >= 'Kim' AND name < 'Lee’ 와 같은 범위 검색에서

  • 단계 1: “어디서부터 읽을지 찾는 과정” 단계
    트리 위에서 아래로 내려가면서 시작 지점을 찾는다.
  • 단계 2: 스캔 단계
    시작 리프 노드를 찾았으면 리프 노드 안에서 정렬된 순서대로 쭉 읽는다.
    리프 노드는 서로 링크(포인터) 로 연결되어 있어서, 한 리프 노드를 끝까지 읽으면 링크를 타고 다음 리프 노드로 이동해서 계속 읽음
  • 단계 3: 실제 데이터 읽기 단계 👉 이 단계에서 랜덤 I/O 발생
    인덱스에는 보통 키 값과 레코드 주소(PK 또는 ROWID)
    • 인덱스에서 얻은 주소를 이용해
    • 실제 데이터 페이지를 다시 읽음
    • 최종 레코드 반환

언제 3번째 “실제 데이터 읽기” 과정이 필요 없을까?

  • 쿼리가 요구하는 컬럼이 모두 인덱스 안에 들어 있다면, 실제 데이터 페이지를 읽을 필요 없음 👉 이걸 커버링 인덱스라고 한다.


인덱스 풀 스캔

인덱스 풀 스캔은 인덱스의 처음부터 끝까지 전부 읽는 방식이다. 즉 범위를 지정하지 않고 인덱스 전체를 스캔하는 방식이다.

다음 조건일 때 자주 사용된다.

  • WHERE 조건이 인덱스를 못 탐
  • 하지만 SELECT에 필요한 컬럼이 인덱스에 다 있음
  • 즉, 커버링 인덱스일 때만 의미 있음

왜 테이블 풀 스캔보다 빠를까?

  • 인덱스 크기 < 테이블 크기
  • 인덱스는 필요한 컬럼만 저장
  • 디스크에서 읽어야 할 양이 더 적음


루스 인덱스 스캔

루스 인덱스 스캔은 인덱스를 범위로 읽되 중간의 불필요한 값은 건너뛰면서(SKIP) 읽는 방식이다.
즉, 필요한 그룹의 대표값만 읽고 다음 그룹으로 점프한다.

  • 주로 그룹별로 하나의 값만 필요할 때, 사용된다.
  • 대표적으로 GROUP BY, MIN(), MAX() 조합으로 사용.
SELECT dept_no, MIN(emp_no)
FROM dept_emp
WHERE dept_no BETWEEN 'd002' AND 'd004'
GROUP BY dept_no;
  • 인덱스: (dept_no, emp_no) → dept_no 기준으로 정렬, 그 안에서 emp_no 정렬
d002, 1001
d002, 1005
d002, 1010
d003, 2001
d003, 2007
d004, 3002
  • 위 상황에서 각 그룹의 emp_no의 최소값만 필요함으로
    • d002 → 첫 번째 값만 읽으면 됨 (1001)
    • d003 → 첫 번째 값만 읽으면 됨 (2001)
    • d004 → 첫 번째 값만 읽으면 됨 (3002)

👉 나머지는 읽을 필요 없으로 중간 값 SKIP


인덱스 스킵 스캔

인덱스 스킵 스캔은 Left-most 규칙을 “우회”하는 MySQL 8.0 기능이다.

인덱스가 이렇게 있다고 가정: INDEX idx_gender_birthdate (gender, birth_date)
보통 B-Tree는 왼쪽 컬럼부터 조건이 있어야 인덱스를 탈 수 있다.
하지만 MySQL 8.0 부터는

SELECT gender, birth_date
FROM employees
WHERE birth_date >= '1965-02-01';

앞 컬럼(gender)의 가능한 값들을 하나씩 넣어서 여러 번 range 스캔을 수행

  • gender는 ENUM('M', 'F') 가능한 값 2개
  • 옵티마이저는 내부적으로 이런 식으로 처리
-- 1번 실행
WHERE gender = 'M'
  AND birth_date >= '1965-02-01'

-- 2번 실행
WHERE gender = 'F'
  AND birth_date >= '1965-02-01'
    1.  
  • range 스캔 가능 👉 결과를 합침

단점 (제약 조건 2가지)

1. 선행 컬럼 유니크 값이 적어야 함

  • 만약 gender (200개) 200번 range 스캔 수행 👉 오히려 비효율적

2. 커버링 인덱스여야 유리

  • 매번 PK까지 내려가면 랜덤 I/O 폭발 👉 skip scan 이점 사라짐