[sqlp]/헷갈리는 부분 정리

복합인덱스 컬럼순서

soo- 2026. 5. 25. 20:13

복합 인덱스에서 = 조건 컬럼의 순서는 왜 상관없을까?

복합 인덱스를 설계하다 보면 흔히 "선택도(Selectivity)가 높은 컬럼을 앞에 둬라"는 조언을 듣는다. 하지만 이 말이 항상 맞는 건 아니다. 적어도 = 조건으로 묶인 컬럼들 사이에서는, 어떤 컬럼이 앞에 오든 인덱스 스캔 효율에 영향을 주지 않는다

이 글에서는 그 이유를 B-Tree 인덱스의 수직 탐색 / 수평 탐색 관점에서 데이터를 직접 펼쳐보며 정리한다

예제 상황

다음과 같은 조건절을 처리한다고 하자.

WHERE 고객등급 = :V1   -- = 조건
AND   고객번호 = :V2   -- = 조건
AND   거래일자 >= :V3  -- 범위(BETWEEN) 조건

고객등급과 고객번호는 항상 = 조건이고 거래일자는 범위 조건이다. 이때 인덱스를 다음 둘 중 어떻게 구성해도 결과가 같은지 살펴본다
  • 인덱스 A: (고객등급, 고객번호, 거래일자)
  • 인덱스 B: (고객번호, 고객등급, 거래일자)

B-Tree 인덱스 검색의 두 단계

먼저 인덱스 검색이 어떻게 동작하는지 짚고 가자. B-Tree 인덱스 검색은 두 단계로 이뤄진다

  1. 수직 탐색: 루트 → 브랜치 → 리프 블록까지 내려가며 스캔 시작점을 찾는 과정
  2. 수평 탐색: 리프 블록을 따라 옆으로 읽으며 데이터를 훑는 과정

핵심 질문은 이거다. "컬럼 순서가 바뀌면 이 두 탐색의 비용이 달라지는가?"

데이터를 직접 펼쳐보자

동일한 데이터를 두 인덱스로 각각 정렬해본다. 복합 인덱스는 사전(辭典) 순서처럼 앞 컬럼을 우선으로 정렬하고 그 값이 같은 행끼리 다음 컬럼을 정렬한다

인덱스 A: (고객등급, 고객번호, 거래일자)

A - 100 - 2024-01-15   ← 등급=A, 번호=100 묶음 시작
A - 100 - 2024-03-20
A - 100 - 2024-05-10   ← 묶음 끝 (3건)
A - 200 - 2024-02-10
A - 200 - 2024-04-05
B - 100 - 2024-01-05
B - 100 - 2024-06-20
B - 150 - 2024-04-12

조건 등급=A AND 번호=100을 만족하는 행은 위에서 3건이 연속으로 뭉쳐 있다

인덱스 B: (고객번호, 고객등급, 거래일자)

100 - A - 2024-01-15   ← 번호=100, 등급=A 묶음 시작
100 - A - 2024-03-20
100 - A - 2024-05-10   ← 묶음 끝 (3건)
100 - B - 2024-01-05
100 - B - 2024-06-20
150 - B - 2024-04-12
200 - A - 2024-02-10
200 - A - 2024-04-05

순서가 바뀌었는데도 조건 `번호=100 AND 등급=A`를 만족하는 행은 여전히 똑같은 3건이 연속으로 뭉쳐 있다

왜 같은 3건이 뭉칠까?

등급=A 그리고 번호=100인 행은 원본 테이블에 어차피 3건밖에 없다.

= 조건이 모두 만족되는 행의 개수는 데이터 자체의 특성이지 인덱스 컬럼 순서가 만들어내는 특성이 아니다. 컬럼 순서를 바꿔도 이 교집합 자체는 변하지 않는다

그리고 복합 인덱스의 성질상 앞쪽 = 조건 컬럼들이 모두 같은 값을 가지는 행들은 인덱스 내에서 반드시 연속된 위치에 모인다 그 묶음 안에서 마지막 컬럼(거래일자)은 항상 정렬되어 있다

인덱스 A: [A-100-?, A-100-?, A-100-?]  ← 3건 뭉침, 거래일자 정렬
인덱스 B: [100-A-?, 100-A-?, 100-A-?]  ← 3건 뭉침, 거래일자 정렬

비유: 학생 정렬

학생들을 정렬한다고 생각해보자.

  • 방법 1: 학년 → 반 → 번호 순 → "3학년 1반" 학생들이 한 곳에 뭉친다 (30명)
  • 방법 2: 반 → 학년 → 번호 순 → "1반 3학년" 학생들이 한 곳에 뭉친다 (30명)

3학년 1반 학생은 어차피 30명이다 어떻게 정렬하든 30명이 한 묶음으로 뭉치고, 그 안에서 번호순으로 정렬된다. 정렬 기준의 순서를 바꿔도 "특정 학년 + 특정 반" 학생 수는 그대로다

그래서 수직 탐색도, 수평 탐색도 동일하다

두 인덱스를 같은 조건으로 검색하면 다음이 모두 같다.

항목 인덱스 A 인덱스 B
수직 탐색 블록 수 루트 1 → 브랜치 1 → 리프 1 루트 1 → 브랜치 1 → 리프 1
묶음(스캔 대상) 크기 3건 3건
거래일자 정렬 여부 정렬됨 정렬됨
수평 탐색 범위 동일 동일

= 조건은 B-Tree에서 단일 진입점을 만든다. 어느 컬럼을 먼저 비교하든 결국 "등급=A 그리고 번호=100인 첫 행"이라는 하나의 지점에 도달한다 그 지점부터 거래일자 범위만큼 옆으로 읽으면 끝이다 그래서 거래일자의 수평 탐색 개수까지 완전히 동일해진다

범위 조건은 왜 반드시 뒤로 보내야 할까?


= 조건끼리는 순서가 무관하지만, 범위 조건은 다르다 범위 조건은 한 점이 아니라 구간을 만들기 때문이다
만약 인덱스를 `(고객등급, 거래일자, 고객번호)`로 구성하면 `거래일자 >= ...`로 점프한 구간 안에서는 `고객번호`가 정렬되어 있지 않다
A - 2024-02-01 - 200
A - 2024-02-05 - 100   ← 번호=100? 맞음
A - 2024-03-10 - 200   ← 번호=100? 아님 (버림)
A - 2024-04-05 - 100   ← 번호=100? 맞음
A - 2024-05-22 - 200   ← 번호=100? 아님 (버림)

이 경우 객번호=100은 인덱스 스캔 범위를 좁히지 못하고 스캔한 행 중에서 버릴지 말지를 판단하는 필터 조건으로 전락한다 범위 구간 전체를 다 읽어야 하므로 비효율적이다

액세스 조건 vs 필터 조건

구분 의미 인덱스 스캔 범위
액세스 조건 인덱스 스캔의 시작점/끝점을 결정 좁힘
필터 조건 스캔한 행에서 버릴지 결정 못 좁힘

복합 인덱스에서 컬럼은 다음 규칙으로 동작한다.
  • = 조건이 연속되는 동안 → 액세스 조건
  • 첫 번째 범위 조건 → 액세스 조건 (자기 자신까지는 범위를 좁힘)
  • 범위 조건 뒤의 컬럼들 → 필터 조건으로 전락

그래서 거래일자처럼 범위 조건인 컬럼은 액세스 조건 컬럼들의 맨 뒤 에 두는 것이 원칙이다
IDX: 고객등급(=) + 고객번호(=) + 거래일자(BETWEEN) + 거래유형 + 상품번호
     └──── 모두 액세스 조건 ────┘                    └─ 필터 ─┘

정리

  • 조건들의 교집합 행 개수는 데이터의 특성이지 인덱스 컬럼 순서의 특성이 아니다
  • 어떤 순서로 정렬하든 그 교집합 행들은 연속으로 뭉치며 마지막 컬럼은 그 묶음 안에서 항상 정렬되어 있다
  • 따라서 = 조건 컬럼들끼리는 순서를 바꿔도 수직 탐색 비용, 수평 탐색 범위가 모두 동일하다
  • 범위 조건은 반드시 액세스 조건 뒤로 보내야 한다 범위 뒤의 컬럼은 정렬 효과를 잃고 필터 조건으로 전락하기 때문이다

"선택도 높은 컬럼을 앞에"라는 조언은 범위 조건이나 IN 조건이 섞일 때 의미가 생긴다 = 조건만으로 묶인 컬럼들 사이에서는 순서를 고민할 필요가 없다

'[sqlp] > 헷갈리는 부분 정리' 카테고리의 다른 글

소트 머지 조인  (0) 2026.05.25
NL조인, 인덱스, 디스크 I/O  (1) 2026.05.16