티스토리 뷰
DB 인덱스(Index) 원리부터 B-Tree 구조, 복합 인덱스 설정 기준 완벽 정리
데이터베이스를 사용하는 백엔드 개발자라면 서비스 규모가 커질수록 반드시 직면하게 되는 숙제가 있습니다. 바로 "느려진 조회(SELECT) 쿼리 속도 개선"입니다. 초기 프로젝트에서는 데이터가 적어 인덱스 없이도 빠르게 작동하지만, 데이터가 수십만, 수천만 건으로 쌓이기 시작하면 인덱스(Index) 설계에 따라 시스템의 운명이 갈리게 됩니다.
흔히 인덱스를 "책의 맨 뒤에 있는 색인(부록)"에 비유하곤 합니다. 하지만 실무에서는 "인덱스를 걸었는데 왜 쿼리가 여전히 느리지?", "복합 인덱스의 컬럼 순서는 어떻게 잡아야 하지?"라는 의문이 생겨 구글을 검색하곤 하죠. 오늘은 구글 알고리즘과 시니어들이 강조하는 DB 인덱스의 내부 작동 원리(B-Tree)부터 실무 최적화 전략까지 심도 있게 파헤쳐 보겠습니다.
📌 핵심 요약 미리보기
- 인덱스의 본질: 데이터의 물리적 주소(RowID)와 컬럼 값을 쌍으로 정렬해 두어 탐색 속도를
O(N)에서 O(log N)으로 줄이는 기술 - B-Tree 구조: Root - Branch - Leaf 노드로 구성되어 어떤 데이터를 찾더라도 동일한 탐색 시간을 보장하는 균형 트리 방식
- 복합 인덱스 규칙: 카디널리티(Cardinality, 중복도가 낮은 컬럼)가 높은 순서대로 왼쪽부터 배치해야 탐색 효율 극대화
- 트레이드 오프: 무분별한 인덱스 생성은
INSERT, UPDATE, DELETE성능을 심각하게 저하시킴
1. 데이터베이스 인덱스의 핵심 메커니즘과 B-Tree 구조
만약 인덱스가 없는 테이블에서 특정 조건의 데이터를 찾으려면, 디스크에 저장된 테이블 블록을 처음부터 끝까지 다 뒤져야 하는 Full Table Scan이 발생합니다. 반면 인덱스를 생성하면 대부분의 RDBMS(MySQL, Oracle 등)는 내부적으로 **B-Tree(Balanced Tree)** 구조의 인덱스 테이블을 별도로 생성합니다.
B-Tree 구조의 핵심은 다음과 같습니다.
- Root Node (루트 노드): 탐색의 시작점이 되는 최상위 노드이며, 하위 브랜치 노드의 주소를 가집니다.
- Branch Node (브랜치 노드): 루트와 리프를 연결하는 중간 다리 역할을 하며, 데이터의 범위 정보를 가지고 경로를 안내합니다.
- Leaf Node (리프 노드): 최하위에 위치하며, '실제 정렬된 컬럼 값'과 '해당 레코드가 저장된 실제 물리적 디스크 주소(RowID)'를 쌍으로 가지고 있습니다.
이 트리 구조 덕분에 수천만 건의 데이터가 있어도 단 몇 번의 노드 이동(O(log N))만으로 원하는 데이터가 있는 물리적 주소로 다이렉트 점프(Index Scan)를 할 수 있게 됩니다.
2. 실무 고비: 복합 인덱스(Composite Index) 생성 시 컬럼 순서 결정 원칙
실무에서는 단 하나의 컬럼으로만 조회하기보다, WHERE status = 'ACTIVE' AND user_id = 500 처럼 여러 조건을 동시에 묶어 조회하는 경우가 훨씬 많습니다. 이때 사용하는 것이 두 개 이상의 컬럼을 묶는 **복합 인덱스(다중 컬럼 인덱스)**입니다.
많은 주니어 개발자분들이 컬럼 순서를 무작위로 두어 인덱스가 제대로 작동하지 않는 실수를 범하곤 합니다. 복합 인덱스를 설계할 때 반드시 지켜야 할 우선순위 기준은 딱 하나, 바로 카디널리티(Cardinality)입니다.
| 구분 | 카디널리티가 높은 컬럼 (Good) | 카디널리티가 낮은 컬럼 (Bad) |
|---|---|---|
| 개념 정의 | 값의 중복도가 낮아 개별 데이터가 잘 식별됨 | 값의 중복도가 높아 식별이 잘 안 됨 |
| 실제 예시 | 주민등록번호, 이메일, 회원 ID, 주문번호 | 성별, 회원 상태(활성/탈퇴), 주문 여부(Y/N) |
| 인덱스 배치 순서 | ⭐ 항상 복합 인덱스의 "왼쪽(앞순서)" 배치 | 복합 인덱스의 "오른쪽(뒷순서)" 배치 |
이유는 간단합니다. 중복도가 낮아 한 번에 필터링을 크게 해줄 수 있는 컬럼(예: 주민번호)을 인덱스 앞단에 두어야 트리 탐색 시 걸러지는 데이터가 많아져 탐색 효율이 기하급수적으로 좋아지기 때문입니다. 반대로 성별을 맨 앞에 두면 트리 첫 레벨에서 데이터가 절반밖에 걸러지지 않아 인덱스 효율이 극도로 떨어집니다.
3. 구글링 단골 검색어: "인덱스를 걸었는데 왜 안 타나요?" (주의해야 할 안티패턴)
분명히 컬럼에 인덱스를 걸어두었는데도 실행 계획(EXPLAIN)을 뜯어보면 Full Scan을 하고 있는 경우가 있습니다. 구글에 "Index 안 타는 원인"을 치기 전에 내가 작성한 SQL에 아래 안티패턴이 없는지 점검해야 합니다.
SELECT * FROM users WHERE AGE * 10 > 300; -- ❌ 안 탐
SELECT * FROM users WHERE AGE > 300 / 10; -- ⭕ 우측 항을 연산해야 인덱스 작동
// 안티패턴 2: 문자열 컬럼 조회 시 앞부분에 와일드카드(%)를 붙이는 경우
SELECT * FROM products WHERE name LIKE '%아이폰'; -- ❌ 안 탐 (B-Tree는 왼쪽부터 정렬됨)
SELECT * FROM products WHERE name LIKE '아이폰%'; -- ⭕ 정상 작동
// 안티패턴 3: 복합 인덱스의 첫 번째 컬럼을 WHERE 조건에서 누락한 경우
// (인덱스가 [category, price] 순서로 묶여 있을 때)
SELECT * FROM goods WHERE price = 10000; -- ❌ 카테고리 조건이 없으므로 인덱스 무시됨
4. 인덱스의 부작용: 쓰기(CUD) 성능과의 트레이드 오프
조회 속도가 빨라진다고 해서 모든 컬럼에 인덱스를 마구잡이로 설정하면 심각한 성능 저하의 부메랑을 맞게 됩니다. 인덱스는 공짜가 아닙니다.
새로운 레코드가 저장될 때(INSERT), 기존 데이터가 수정될 때(UPDATE), 삭제될 때(DELETE) 데이터베이스는 테이블 본체뿐만 아니라 정렬되어 보관 중인 인덱스 테이블의 내부 데이터 구조까지 매번 재정렬하고 노드를 분할(Page Split)하는 추가 연산을 해야 합니다.
따라서 조회 빈도수와 쓰기 빈도수의 비율을 면밀히 파악하고, 너무 많은 인덱스가 걸려있지는 않은지 정기적으로 모니터링해야 훌륭한 시스템 백엔드를 유지할 수 있습니다.
글을 마치며: EXPLAIN 명령어를 친숙하게 대하세요
데이터베이스 성능 최적화의 첫걸음은 추측이 아니라 **'확인'**입니다. 실무에서 조금이라도 쿼리가 무겁게 느껴진다면 쿼리문 앞에 EXPLAIN을 붙여 DB 엔진이 어떤 인덱스를 선택했는지, 풀 스캔을 돌리지는 않는지 눈으로 확인하는 습관을 지녀보세요. B-Tree 메커니즘을 온전히 이해하고 쿼리를 짜는 개발자야말로, 연차가 쌓여도 흔들리지 않는 튼튼한 인프라 역량을 가질 수 있을 것입니다.
#DB인덱스원리 #BTree인덱스 #복합인덱스순서 #인덱스안타는이유 #쿼리성능최적화 #FullTableScan #카디널리티 #데이터베이스튜닝
'데이터베이스' 카테고리의 다른 글
| DB 파티셔닝(Partitioning)과 샤딩(Sharding) 원리 및 알고리즘 (0) | 2026.06.07 |
|---|---|
| JPA N+1 문제 원인 및 해결 방법: Fetch Join과 Batch Size 완벽 정리 (0) | 2026.06.04 |
| SQL JOIN 종류 및 예제 (0) | 2026.05.24 |
| 데이터베이스 정규화 BCNF (0) | 2026.05.17 |
| 데이터베이스 제3정규화 (0) | 2026.05.17 |
| 데이터베이스 제2정규화 (0) | 2026.05.17 |
| 데이터베이스 제1정규화 (0) | 2026.05.17 |
| 가장 많이 사용된 데이터베이스(DBMS) TOP10 - 2026 최신버전 (0) | 2026.05.07 |
- Total
- Today
- Yesterday
- MySQL
- 아두이노
- 정보처리기사
- 블루투스
- OpenCV
- Java
- 안드로이드
- Class
- c#
- C언어
- 벡터
- 문제풀이
- Android
- 파이썬
- 파일처리
- DB연동
- C
- 데이터베이스
- 문자열
- C++
- 클래스
- String
- C++ 클래스
- 알고리즘
- 상속
- html
- 리스트
- 자바
- 자료구조
- 배열
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | |||
| 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| 12 | 13 | 14 | 15 | 16 | 17 | 18 |
| 19 | 20 | 21 | 22 | 23 | 24 | 25 |
| 26 | 27 | 28 | 29 | 30 | 31 |