STUDY NOTE · 개념 정리
DB 인덱스 개념 정리: B+Tree, 클러스터드 vs 논클러스터드, 복합 인덱스
Database
인덱스는 특정 컬럼 값을 정렬해 둔 별도의 자료구조로, 책의 색인처럼 원하는 데이터의 위치를 빠르게 찾게 해준다. 조회는 빨라지지만 저장 공간과 쓰기 성능을 대가로 낸다.
1. 인덱스가 없으면
WHERE email = 'a@b.com'을 실행하면 DB는 테이블의 모든 행을 처음부터 끝까지 읽으며 비교한다(Full Table Scan). 100만 건이면 100만 번 비교한다. 인덱스가 있으면 정렬된 구조에서 몇 번 만에 위치를 찾는다.
2. 인덱스의 장단점
| 구분 | 내용 |
| 장점 | 조건 검색(WHERE), 정렬(ORDER BY), 그룹화(GROUP BY), 조인 속도 향상 |
| 단점 | 추가 저장 공간 필요 |
| 단점 | INSERT / UPDATE / DELETE 시 인덱스도 함께 수정해야 해서 쓰기가 느려짐 |
| 단점 | 잘못 만들면 옵티마이저가 쓰지 않거나 오히려 느려짐 |
3. 자료구조: 왜 B+Tree인가
| 자료구조 | 등호 검색 | 범위 검색 | 특징 |
| 해시 테이블 | O(1) | 불가 | 정렬이 없어 범위·정렬에 못 씀 |
| 이진 탐색 트리 | O(log n) | 가능 | 한 노드에 값 1개라 트리가 깊어짐 (디스크 접근 많음) |
| B-Tree | O(log n) | 가능 | 한 노드에 여러 값, 트리가 낮음 |
| B+Tree | O(log n) | 매우 유리 | 데이터는 리프에만, 리프끼리 연결 리스트 |
DB는 디스크(페이지) 단위로 읽기 때문에 트리 높이가 낮을수록 디스크 접근이 줄어든다. B+Tree는 노드 하나에 수백 개의 키를 담아 100만 건도 높이 3~4 정도로 끝난다. 또 리프 노드가 서로 연결돼 있어 범위 검색(BETWEEN, >) 시 시작점만 찾고 옆으로 읽으면 된다. MySQL InnoDB를 비롯한 대부분의 RDBMS가 B+Tree를 쓴다.
4. 클러스터드 vs 논클러스터드 인덱스
| 구분 | 클러스터드 인덱스 | 논클러스터드(세컨더리) 인덱스 |
| 개수 | 테이블당 1개 | 여러 개 가능 |
| 정렬 대상 | 테이블 데이터 자체가 이 순서로 저장 | 인덱스만 별도로 정렬 |
| InnoDB에서 | PK (없으면 UNIQUE NOT NULL, 그것도 없으면 내부 키) | PK 외의 인덱스 |
| 리프 노드 내용 | 실제 행 데이터 | 인덱스 컬럼 값 + PK 값 |
| 조회 과정 | 한 번에 행에 도달 | 인덱스에서 PK를 얻고 → 클러스터드 인덱스를 다시 탐색 |
세컨더리 인덱스로 찾은 행이 아주 많으면 "PK로 다시 찾아가기"가 그만큼 반복된다. 이 경우 옵티마이저는 인덱스를 버리고 풀 스캔을 고르기도 한다.
5. 복합 인덱스와 최좌측 접두사 규칙
INDEX (a, b, c)는 a로 정렬하고, a가 같으면 b로, b까지 같으면 c로 정렬한 구조다. 전화번호부가 성 → 이름 순으로 정렬된 것과 같다.
| 조건 | 인덱스 사용 |
| WHERE a = ? | 사용 |
| WHERE a = ? AND b = ? | 사용 |
| WHERE a = ? AND b = ? AND c = ? | 모두 사용 |
| WHERE b = ? | 불가 (첫 컬럼 a가 없음) |
| WHERE a = ? AND c = ? | a까지만 범위 축소, c는 필터로만 |
| WHERE a > ? AND b = ? | a 범위 이후의 b는 정렬 순서를 활용 못 함 |
설계 원칙은 두 가지다.
- 등호(=) 조건 컬럼을 앞에, 범위 조건 컬럼을 뒤에 둔다.
- 등호 조건끼리는 카디널리티(값의 종류)가 높은 컬럼을 앞에 두는 것이 일반적으로 유리하다.
6. 커버링 인덱스
쿼리에 필요한 컬럼이 모두 인덱스 안에 있어서 테이블 본체를 읽지 않고 인덱스만으로 결과를 내는 경우다. 세컨더리 인덱스 → 클러스터드 인덱스로 가는 과정이 사라져 크게 빨라진다. EXPLAIN의 Extra에 Using index로 표시된다. SELECT *는 커버링을 깨는 가장 흔한 원인이다.
7. 인덱스를 타지 못하는 경우
| 경우 | 예 | 이유 |
| 컬럼을 가공 | WHERE YEAR(created_at) = 2025 | 정렬된 원래 값과 비교할 수 없음 |
| 암묵적 형변환 | 문자열 컬럼에 WHERE phone = 01012345678 | 컬럼 쪽이 변환되어 가공한 것과 같음 |
| 앞부분 와일드카드 | WHERE name LIKE '%고기' | 시작 값을 몰라 정렬을 이용 못 함 |
| 부정 조건 | WHERE status != 'DONE' | 대부분의 행이 해당돼 풀 스캔이 나음 |
| OR 조건 | WHERE a = 1 OR b = 2 | 양쪽 모두 인덱스가 없으면 풀 스캔 |
| 낮은 선택도 | WHERE gender = 'M' | 걸러도 절반이 남아 인덱스 이점이 없음 |
공통 원리는 "인덱스는 가공하지 않은 원래 값의 정렬"이라는 것이다.
8. 인덱스를 걸면 좋은 컬럼
- WHERE, JOIN의 ON, ORDER BY, GROUP BY에 자주 쓰이는 컬럼
- 카디널리티가 높은 컬럼 (회원 ID, 이메일, 주문번호)
- 수정이 잦지 않은 컬럼
9. EXPLAIN 읽는 법
| 항목 | 볼 것 |
| type | const, eq_ref, ref, range는 양호 / index(인덱스 전체 스캔), ALL(풀 스캔)은 주의 |
| key | 실제로 사용한 인덱스 (NULL이면 인덱스 미사용) |
| rows | 읽을 것으로 예상한 행 수 |
| Extra | Using index(커버링, 좋음), Using filesort·Using temporary(별도 정렬·임시 테이블, 주의) |
10. 실제로 어떻게 적용되나
- 회원의 최근 주문 목록:
WHERE member_id = ? ORDER BY ordered_at DESC LIMIT 20쿼리에는(member_id, ordered_at)복합 인덱스를 건다. 등호 컬럼이 앞, 정렬 컬럼이 뒤라서 인덱스 순서대로 20개만 읽고 끝나고 filesort가 사라진다. - 날짜 검색:
DATE(created_at) = '2025-12-09'대신created_at >= '2025-12-09' AND created_at < '2025-12-10'으로 바꾸면 컬럼을 가공하지 않으므로 인덱스를 탄다. - 목록 화면 최적화: 목록에 필요한 몇 개 컬럼만 SELECT하고 그 컬럼들을 인덱스에 포함시켜 커버링 인덱스로 만든다.
- 중복 인덱스 정리:
(a)와(a, b)가 둘 다 있으면(a)는 중복이다.(a, b)가 a 단독 조건도 처리하므로 하나를 지워 쓰기 비용을 줄인다.
11. 면접 질문으로 정리
- Q. 인덱스란? 장단점은? 컬럼 값을 정렬해 둔 별도 자료구조로 검색·정렬 속도를 높인다. 대신 저장 공간이 들고, 데이터 변경 시 인덱스도 갱신해야 해서 쓰기 성능이 떨어진다.
- Q. 왜 B+Tree를 쓰나? 노드당 여러 키를 저장해 트리 높이가 낮아 디스크 접근이 적고, 리프 노드가 연결돼 있어 범위 검색에 유리하기 때문이다. 해시는 등호 검색은 빠르지만 범위 검색과 정렬을 지원하지 못한다.
- Q. 클러스터드 인덱스와 논클러스터드 인덱스의 차이는? 클러스터드 인덱스는 테이블 데이터 자체가 그 순서로 저장되어 테이블당 하나이고, 논클러스터드 인덱스는 별도 구조로 여러 개 만들 수 있으며 실제 행을 찾으려면 한 번 더 탐색한다.
- Q. 복합 인덱스 컬럼 순서는 어떻게 정하나? 최좌측 접두사 규칙 때문에 자주 쓰는 등호 조건 컬럼을 앞에, 범위·정렬 조건 컬럼을 뒤에 둔다.