B+Tree 데이터베이스 인덱스가 왜 B-Tree 대신 선택됐는지 궁금하셨나요? “MySQL 인덱스가 B+Tree 구조라는 건 알겠는데, 왜 굳이 B-Tree가 아닌 B+Tree여야 하나요?” 기술 면접에서도 단골로 등장하고, 실무에서도 인덱스를 올바르게 설계하려면 반드시 이해해야 하는 개념입니다. 이 글에서는 이진 탐색 트리의 한계에서 출발해 B-Tree의 등장 배경, B-Tree와 B+Tree의 구조적 차이, 그리고 데이터베이스가 B+Tree를 선택한 결정적 이유까지 그림과 예시로 단계별로 풀어드립니다. 끝까지 읽으면 인덱스 구조의 전체 그림이 선명하게 잡힐 것입니다.
목차
- 트리 자료구조 기초 – 이진 탐색 트리의 한계까지
- B-Tree의 등장 – 디스크 I/O를 줄이기 위한 핵심 원리
- B-Tree와 B+Tree의 구조적 차이 완벽 비교
- B+Tree가 데이터베이스 인덱스에 최적인 결정적 이유
- 실무에서 B+Tree 인덱스가 작동하는 방식 – MySQL InnoDB 중심
- 전문가 관점 – 인덱스 설계 원칙과 B+Tree 활용 시 주의사항
1. 트리 자료구조 기초 – 이진 탐색 트리의 한계까지
B-Tree와 B+Tree를 이해하려면 먼저 트리(Tree) 자료구조가 무엇인지, 그리고 기존 이진 탐색 트리가 왜 데이터베이스에는 적합하지 않은지부터 출발해야 합니다.
트리 자료구조란
트리는 계층적 구조를 가진 비선형 자료구조입니다. 가장 위의 노드를 루트(Root), 가장 아래의 노드를 리프(Leaf), 그 사이를 **내부 노드(Internal Node)**라고 부릅니다. 트리에서 데이터를 찾을 때는 루트부터 시작해 조건에 따라 왼쪽 또는 오른쪽 자식으로 내려가며 탐색합니다.
[일반 트리 구조]
[루트: 50]
/ \
[30] [70]
/ \ / \
[20] [40] [60] [80]
← 리프 노드(자식 없음)
이진 탐색 트리(BST)의 원리
**이진 탐색 트리(Binary Search Tree, BST)**는 다음 규칙을 따릅니다.
- 왼쪽 자식 < 부모 노드 < 오른쪽 자식
- 각 노드는 최대 2개의 자식을 가짐
이 구조 덕분에 탐색이 매우 효율적입니다. 노드가 N개일 때 평균 탐색 시간은 **O(log N)**입니다. 값 40을 찾는다면 50(루트) → 30(왼쪽) → 40(오른쪽) 순서로 단 3번만에 찾습니다.
BST의 치명적 약점 – 편향 트리
BST의 문제는 데이터가 정렬된 순서로 삽입될 때 발생합니다.
[순서대로 10, 20, 30, 40, 50 삽입 시]
[10]
\
[20]
\
[30]
\
[40]
\
[50]
→ 트리가 한쪽으로 기울어져(편향)
→ 탐색 시간이 O(log N) → O(N)으로 악화
→ 사실상 선형 탐색(링크드 리스트)과 동일한 성능
데이터베이스에 순차적으로 데이터가 삽입되는 경우는 매우 흔합니다(자동 증가 PK, 시간순 로그 등). BST만으로는 이 문제를 막을 수 없습니다.
디스크 기반 저장소에서 BST가 가진 또 다른 문제
데이터베이스는 데이터를 **디스크(HDD/SSD)**에 저장합니다. 디스크 I/O(읽기·쓰기)는 메모리 접근보다 수천 배 느립니다. BST에서 탐색할 때 노드 하나를 읽을 때마다 디스크 접근이 발생할 수 있습니다.
[BST와 디스크 I/O의 문제]
백만 개의 데이터(2^20)에서 BST 탐색 시:
→ 최대 20번의 노드 탐색 필요
→ 각 노드가 디스크의 서로 다른 위치에 저장
→ 최대 20번의 랜덤 디스크 I/O 발생
디스크 랜덤 I/O 1회 ≈ 10ms
20번 × 10ms = 200ms → 0.2초
→ 실시간 조회가 필요한 데이터베이스에서는 치명적
이 두 가지 문제(편향 문제 + 과도한 디스크 I/O)를 동시에 해결하기 위해 등장한 것이 바로 B-Tree입니다.
2. B-Tree의 등장 – 디스크 I/O를 줄이기 위한 핵심 원리
**B-Tree(Balanced Tree)**는 1972년 Rudolf Bayer와 Ed McCreight가 발표한 자료구조입니다. 이름의 ‘B’가 무엇을 의미하는지는 공식적으로 발표된 적이 없지만, Balanced(균형), Broad(넓은), Boeing(개발 당시 근무 기업) 등 여러 설이 있습니다. 핵심은 항상 균형 잡힌 트리를 유지한다는 점입니다.
B-Tree의 핵심 특성
B-Tree는 이진 탐색 트리와 달리 각 노드가 여러 개의 키(Key)와 여러 개의 자식을 가질 수 있습니다. 이를 M차 B-Tree라고 하며, M은 하나의 노드가 가질 수 있는 최대 자식 수입니다.
M차 B-Tree의 규칙:
✅ 모든 리프 노드는 같은 깊이(레벨)에 위치 → 균형 보장
✅ 루트를 제외한 모든 노드는 최소 ⌈M/2⌉개의 자식을 가짐
✅ 하나의 노드는 최대 M-1개의 키를 가짐
✅ 각 노드의 키는 오름차순 정렬됨
✅ 내부 노드의 키들은 자식 노드의 범위를 구분하는 경계값 역할
3차 B-Tree 구조 예시
[3차 B-Tree – 노드당 최대 2개의 키, 최대 3개의 자식]
[30 | 70]
/ | \
[10|20] [40|60] [80|90]
→ 30보다 작으면 왼쪽 자식 탐색
→ 30~70 사이면 가운데 자식 탐색
→ 70보다 크면 오른쪽 자식 탐색
모든 리프 노드가 같은 깊이 → 균형 트리 유지
B-Tree가 디스크 I/O를 줄이는 원리
B-Tree의 가장 큰 혁신은 노드 하나에 많은 키를 담는 것입니다. 데이터베이스는 디스크를 페이지(Page) 단위(보통 4KB~16KB)로 읽습니다. 노드 하나를 페이지 크기에 맞추면, 디스크 I/O 한 번으로 수백 개의 키를 메모리로 올릴 수 있습니다.
[BST vs B-Tree 디스크 I/O 비교]
데이터 100만 개 기준:
BST (이진 탐색):
트리 높이 ≈ log₂(1,000,000) ≈ 20
→ 최대 20번 디스크 I/O
B-Tree (차수 1000 – 페이지당 1000개의 키):
트리 높이 ≈ log₁₀₀₀(1,000,000) ≈ 2
→ 최대 2~3번 디스크 I/O
→ 디스크 접근 횟수를 20번 → 2~3번으로 대폭 감소
→ 이것이 B-Tree가 데이터베이스에 적합한 핵심 이유
이렇게 B-Tree는 편향 문제와 과도한 디스크 I/O 문제를 동시에 해결했습니다. 그런데 왜 데이터베이스는 B-Tree 대신 B+Tree를 선택했을까요?
3. B-Tree와 B+Tree의 구조적 차이 완벽 비교
B+Tree는 B-Tree를 개선한 변형 구조입니다. 겉보기에는 비슷하지만, 데이터베이스 환경에서 결정적인 차이를 만드는 두 가지 핵심 변경점이 있습니다.
핵심 차이 1 – 데이터의 위치
B-Tree: 내부 노드와 리프 노드 모두에 실제 데이터(또는 데이터 포인터)를 저장합니다.
B+Tree: 리프 노드에만 실제 데이터를 저장합니다. 내부 노드(루트·브랜치)는 탐색을 위한 키(인덱스)만 가집니다.
[B-Tree 구조 – 내부 노드에도 데이터 존재]
[30,data | 70,data] ← 내부 노드에도 data 저장
/ | \
[10,d|20,d] [40,d|60,d] [80,d|90,d] ← 리프 노드에도 data 저장
(d = data 또는 데이터 포인터)
[B+Tree 구조 – 리프 노드에만 데이터 존재]
[30 | 70] ← 내부 노드: 키(인덱스)만 저장
/ | \
[10,d|20,d|30,d] → [40,d|60,d|70,d] → [80,d|90,d]
← 리프 노드: 실제 data 저장
← 리프 노드끼리 연결 리스트로 연결 (→)
핵심 차이 2 – 리프 노드의 연결
B-Tree: 리프 노드 사이에 연결 고리가 없습니다. 각 리프 노드는 독립적입니다.
B+Tree: 모든 리프 노드가 **연결 리스트(Linked List)**로 순서대로 연결됩니다. 왼쪽에서 오른쪽으로 순차 탐색이 가능합니다.
두 구조의 전면 비교표
| 구분 | B-Tree | B+Tree |
|---|---|---|
| 데이터 저장 위치 | 모든 노드(내부+리프) | 리프 노드에만 저장 |
| 내부 노드 역할 | 탐색 + 데이터 저장 | 탐색(인덱스)만 담당 |
| 리프 노드 연결 | 연결 없음 | 연결 리스트로 순서 연결 |
| 내부 노드 용량 | 키 + 데이터로 노드 크기 큼 | 키만 저장하여 노드 크기 작음 |
| 같은 페이지에 저장 가능한 키 수 | 적음 | 많음 (팬아웃 높음) |
| 트리 높이 | 상대적으로 높음 | 상대적으로 낮음 |
| 특정값 탐색 | 내부 노드에서도 찾을 수 있어 빠를 수 있음 | 항상 리프까지 내려가야 함 |
| 범위 탐색 | 트리를 다시 순회해야 함 | 리프 연결 리스트로 선형 탐색 가능 |
| 순차 탐색 | 비효율적 | 매우 효율적 |
| 주요 사용처 | 파일 시스템 일부 | RDBMS 인덱스 (MySQL, Oracle, PostgreSQL) |
탐색 경로 비교 시뮬레이션
값 40을 찾는 경우를 비교해 봅니다.
[B-Tree에서 40 탐색]
루트 [30,data | 70,data]
└─ 30 < 40 < 70 → 가운데 자식으로
[40,data | 60,data]
└─ 40 발견! data 즉시 반환
→ 2번 접근으로 완료 ✅
[B+Tree에서 40 탐색]
루트 [30 | 70]
└─ 30 < 40 < 70 → 가운데 자식으로
리프 [40,data | 60,data | 70,data]
└─ 40 발견! data 반환
→ 2번 접근으로 완료 ✅
→ 단일 값 탐색 성능은 유사
단일 값 탐색은 B-Tree가 내부 노드에서 조기에 발견할 수도 있어 이론상 약간 유리합니다. 하지만 실제 데이터베이스 환경에서는 B+Tree가 압도적으로 유리한 상황이 훨씬 많습니다. 그 이유가 바로 다음 섹션의 핵심입니다.
4. B+Tree가 데이터베이스 인덱스에 최적인 결정적 이유
B+Tree 데이터베이스 인덱스가 선택된 이유는 단 하나의 장점 때문이 아닙니다. 데이터베이스가 실제로 어떤 작업을 가장 많이 수행하는지를 분석하면, B+Tree의 모든 설계 결정이 그 작업들에 최적화되어 있다는 것을 알 수 있습니다.
이유 ① 범위 검색(Range Query)의 압도적 효율
데이터베이스에서 가장 자주 실행되는 쿼리 유형 중 하나는 범위 검색입니다.
sql
-- 실무에서 매우 흔한 범위 검색 쿼리들
SELECT * FROM orders WHERE order_date BETWEEN '2024-01-01' AND '2024-12-31';
SELECT * FROM products WHERE price >= 10000 AND price <= 50000;
SELECT * FROM users WHERE age >= 20 AND age <= 30;
SELECT * FROM logs WHERE created_at > '2024-06-01' ORDER BY created_at;
B-Tree에서 범위 검색:
[B-Tree에서 40~80 범위 검색]
루트 [30 | 70]
↓ 40 탐색 → 리프까지 이동 → 40 발견
↓ 다음 값 50 탐색 → 루트부터 다시 탐색 시작!
↓ 60 탐색 → 루트부터 다시!
↓ 70 탐색 → 루트부터 다시!
↓ 80 탐색 → 루트부터 다시!
→ 범위 내 값 N개 → N번 트리 순회 = 매우 비효율적
B+Tree에서 범위 검색:
[B+Tree에서 40~80 범위 검색]
루트 [30 | 70]
↓ 40 탐색 → 리프 노드 도달
↓ 리프 연결 리스트를 따라 오른쪽으로 선형 탐색
[40,d] → [50,d] → [60,d] → [70,d] → [80,d] → 81 도달 → 종료
→ 트리 탐색 1번 + 리프 선형 탐색
→ 디스크 I/O 최소화, 속도 압도적으로 빠름 ✅
실무에서 인덱스를 타는 범위 쿼리의 성능 차이는 수십 배에 달할 수 있습니다.
이유 ② 내부 노드에 더 많은 키를 저장 – 팬아웃(Fan-out) 향상
B+Tree의 내부 노드는 키(인덱스 값)만 저장하고 실제 데이터를 저장하지 않습니다. 이 단순한 차이가 트리의 높이를 낮추는 결정적 요인이 됩니다.
[페이지 크기 16KB 기준 비교]
B-Tree 내부 노드 (키 + 데이터 포인터 저장):
키 1개 크기 ≈ 8byte (정수)
데이터 포인터 ≈ 8byte
→ 노드당 키 수 ≈ 16,384 / 16 ≈ 1,024개
B+Tree 내부 노드 (키만 저장):
키 1개 크기 ≈ 8byte
자식 포인터 ≈ 8byte (키 하나당)
→ 노드당 키 수 ≈ 16,384 / 8 ≈ 2,048개
→ B+Tree 내부 노드에 약 2배 많은 키 저장 가능
→ 팬아웃(하나의 노드에서 뻗어나가는 자식 수) 2배 증가
→ 같은 데이터 수에서 트리 높이가 낮아짐
→ 탐색에 필요한 디스크 I/O 감소
실제 MySQL InnoDB는 페이지 크기가 16KB이며, 내부 노드 하나에 수백~수천 개의 키를 저장합니다. 수억 건의 데이터도 트리 높이 3~4 레벨로 관리할 수 있습니다.
이유 ③ 순차 접근(Sequential Access) 최적화
데이터베이스에서는 ORDER BY, GROUP BY, 전체 테이블 스캔 등 순차적 데이터 접근이 매우 빈번합니다.
sql
SELECT * FROM users ORDER BY age; -- 정렬 출력
SELECT * FROM orders GROUP BY user_id; -- 그룹화
SELECT * FROM products; -- 전체 스캔(풀 스캔)
B+Tree에서는 리프 노드가 연결 리스트로 연결되어 있어 리프 레벨만 순서대로 읽으면 전체 데이터를 정렬된 순서로 접근할 수 있습니다. 디스크에서 연속된 페이지를 순서대로 읽는 것(순차 I/O)은 랜덤 I/O보다 수십 배 빠릅니다.
[B+Tree 순차 접근 흐름]
리프 레벨만 순서대로 스캔:
[10|20|30] → [40|50|60] → [70|80|90] → [100|110] → ...
→ 루트·내부 노드 전혀 접근하지 않음
→ 디스크 순차 읽기 → 최대 I/O 성능 활용
→ ORDER BY 처리 시 별도 정렬 없이 인덱스 스캔으로 대체 가능
이유 ④ 예측 가능하고 일관된 탐색 성능
B-Tree에서는 운이 좋으면 내부 노드에서 데이터를 바로 찾을 수 있지만, B+Tree에서는 항상 리프 노드까지 내려가야 합니다. 이것이 오히려 장점이 됩니다.
[B-Tree 탐색 시간 분포]
루트에서 발견: O(1) → 매우 빠름 (드문 경우)
중간 노드: O(k) → 깊이에 따라 다름
리프 노드: O(log N) → 가장 느림
→ 탐색 시간이 데이터 위치에 따라 불규칙
[B+Tree 탐색 시간 분포]
항상 리프까지 내려감: O(log N) → 항상 일정
→ 모든 데이터에 대해 균등한 탐색 시간 보장
→ 데이터베이스 쿼리 성능 예측 가능 → 안정적인 서비스 운영에 유리
이유 ⑤ 캐시 효율성 향상
내부 노드에 데이터가 없으므로 내부 노드의 크기가 작습니다. 자주 접근하는 루트와 상위 내부 노드들을 메모리(버퍼 풀)에 올려두면, 대부분의 탐색이 메모리에서 처리되고 디스크 I/O는 리프 노드 접근 시에만 발생합니다.
[MySQL InnoDB 버퍼 풀 + B+Tree 캐시 효과]
메모리 (버퍼 풀):
└─ 루트 노드 (항상 캐시)
└─ 2레벨 내부 노드 (자주 캐시)
└─ 자주 접근하는 리프 노드 (LRU 캐시)
디스크 I/O:
└─ 캐시 미스 발생 시 리프 노드만 읽기
→ 99% 이상의 탐색이 1~2번 디스크 I/O로 완료 가능
5. 실무에서 B+Tree 인덱스가 작동하는 방식 – MySQL InnoDB 중심
이론을 실무와 연결합니다. MySQL의 대표 스토리지 엔진인 InnoDB는 B+Tree를 기반으로 두 종류의 인덱스를 운영합니다.
클러스터드 인덱스(Clustered Index) – 테이블 자체가 B+Tree
InnoDB에서 기본 키(Primary Key) 인덱스는 특별합니다. 단순히 “어느 위치에 데이터가 있는지”를 가리키는 포인터가 아니라, 테이블의 실제 데이터 행(Row)이 리프 노드에 직접 저장됩니다. 이를 클러스터드 인덱스(Clustered Index)라고 합니다.
[InnoDB 클러스터드 인덱스 B+Tree 구조]
내부 노드: [PK=100 | PK=500]
/ | \
리프 노드:
[PK=1, name='김철수', age=25, email='kim@...']
[PK=50, name='이영희', age=30, email='lee@...']
→
[PK=100, name='박민준', age=22, email='park@...']
[PK=200, name='최수진', age=35, email='choi@...']
→
[PK=500, name='정다운', age=28, email='jung@...']
→ 기본 키 순서대로 데이터가 물리적으로 정렬되어 저장
→ 기본 키로 조회 시 리프 노드에서 바로 모든 컬럼 데이터 획득
이 때문에 InnoDB에서 기본 키를 INT AUTO_INCREMENT로 설정하면 데이터가 항상 순서대로 삽입되어 B+Tree의 분할(Split)이 최소화되고 성능이 최적화됩니다. 반대로 UUID처럼 무작위 값을 기본 키로 쓰면 삽입 시마다 트리 재구성이 빈번하게 발생합니다.
세컨더리 인덱스(Secondary Index) – 리프에 기본 키를 저장
기본 키 외의 컬럼에 생성하는 인덱스를 **세컨더리 인덱스(보조 인덱스)**라고 합니다. InnoDB의 세컨더리 인덱스는 리프 노드에 데이터 행의 물리적 주소 대신 해당 행의 기본 키 값을 저장합니다.
[세컨더리 인덱스 조회 과정 – name 컬럼에 인덱스 생성 시]
① name='박민준' 검색
↓
② name 세컨더리 인덱스 B+Tree 탐색
리프 노드 발견: name='박민준', PK=100
↓
③ PK=100으로 클러스터드 인덱스(기본 키 인덱스) 재탐색
클러스터드 인덱스 리프에서 전체 row 데이터 반환
→ 이 과정을 "인덱스를 통한 PK 조회 후 테이블 접근" 또는 북마크 룩업(Bookmark Lookup)이라 함
→ 커버링 인덱스(Covering Index) 사용 시 ③ 단계 생략 가능 (성능 최적화)
실행 계획(EXPLAIN)으로 B+Tree 인덱스 동작 확인
sql
-- 인덱스 사용 여부 확인
EXPLAIN SELECT * FROM orders
WHERE order_date BETWEEN '2024-01-01' AND '2024-12-31';
-- 결과 예시
-- type: range → 범위 스캔 (B+Tree 리프 연결 리스트 활용)
-- key: idx_order_date → 사용한 인덱스
-- rows: 3542 → 스캔 예상 행 수
-- Extra: Using index condition → 인덱스 컨디션 푸시다운 적용
-- 비교: 인덱스 없을 때
-- type: ALL → 풀 테이블 스캔 (B+Tree 사용 안 함)
-- rows: 1000000 → 전체 행 스캔
B+Tree 인덱스 활용·미활용 사례
sql
-- ✅ B+Tree 인덱스가 효과적으로 사용되는 패턴
SELECT * FROM users WHERE age = 25; -- 동등 조건
SELECT * FROM users WHERE age >= 20; -- 범위 조건 (한쪽 개방)
SELECT * FROM users WHERE age BETWEEN 20 AND 30; -- 범위 조건 (양쪽 폐쇄)
SELECT * FROM users WHERE name LIKE '김%'; -- 전방 일치 LIKE
SELECT * FROM users ORDER BY age LIMIT 10; -- 정렬 + LIMIT
-- ❌ B+Tree 인덱스가 사용되지 않는 패턴 (주의!)
SELECT * FROM users WHERE YEAR(created_at) = 2024; -- 컬럼에 함수 적용
SELECT * FROM users WHERE age + 1 = 26; -- 컬럼 연산
SELECT * FROM users WHERE name LIKE '%김'; -- 후방 일치 LIKE (전체 스캔)
SELECT * FROM users WHERE age != 25; -- 부정 조건 (경우에 따라 다름)
6. 전문가 관점 – 인덱스 설계 원칙과 B+Tree 활용 시 주의사항
B+Tree 구조를 이해했다면, 이를 바탕으로 실무 인덱스 설계에서 어떤 원칙을 따라야 하는지, 어떤 실수를 피해야 하는지 살펴봅니다.
인덱스 설계 핵심 원칙
① 카디널리티(Cardinality)가 높은 컬럼에 인덱스 생성
카디널리티란 해당 컬럼이 가질 수 있는 유니크한 값의 수입니다. B+Tree는 카디널리티가 높을수록 탐색 범위를 효과적으로 좁혀줍니다.
높은 카디널리티 (인덱스 효과적):
user_id, email, phone_number, order_id → 거의 모든 값이 유니크
낮은 카디널리티 (인덱스 비효과적):
gender (남/여), status (활성/비활성), is_deleted (Y/N)
→ 절반씩 나뉘므로 인덱스로 걸러지는 데이터가 적음
→ 풀 스캔이 오히려 빠를 수도 있음
② 복합 인덱스(Composite Index)의 컬럼 순서
여러 컬럼에 하나의 인덱스를 생성하는 복합 인덱스는 컬럼 순서가 성능을 결정합니다. B+Tree는 인덱스의 가장 왼쪽 컬럼부터 순서대로 작동합니다(선두 컬럼 원칙).
sql
-- 복합 인덱스: (last_name, first_name, age)
CREATE INDEX idx_name_age ON users(last_name, first_name, age);
-- ✅ 인덱스 활용 가능
WHERE last_name = '김'
WHERE last_name = '김' AND first_name = '철수'
WHERE last_name = '김' AND first_name = '철수' AND age = 25
-- ❌ 인덱스 활용 불가 (선두 컬럼 없음)
WHERE first_name = '철수' -- last_name 없이 first_name만
WHERE age = 25 -- last_name 없이 age만
WHERE first_name = '철수' AND age = 25 -- last_name 없이 중간부터
③ 커버링 인덱스(Covering Index)로 성능 극대화
쿼리에서 SELECT하는 모든 컬럼이 인덱스에 포함되어 있으면, 클러스터드 인덱스(테이블)를 추가로 조회하지 않아도 됩니다. 이를 커버링 인덱스라고 하며, 디스크 I/O를 대폭 줄입니다.
sql
-- user_id, name, email로 구성된 복합 인덱스 존재 시
CREATE INDEX idx_cover ON users(user_id, name, email);
-- ✅ 커버링 인덱스 적용 – 인덱스만으로 모든 컬럼 반환
SELECT user_id, name, email FROM users WHERE user_id = 100;
-- EXPLAIN Extra: "Using index" → 테이블 접근 없음
-- ❌ 커버링 인덱스 미적용 – phone_number가 인덱스에 없음
SELECT user_id, name, email, phone_number FROM users WHERE user_id = 100;
-- EXPLAIN Extra: 없음 → 테이블 추가 접근 발생
B+Tree 인덱스의 흔한 실수와 해결책
| 실수 유형 | 문제 | 해결책 |
|---|---|---|
| 과도한 인덱스 생성 | INSERT·UPDATE·DELETE 시 B+Tree 재구성 비용 증가 | 꼭 필요한 컬럼만 선별 |
| 함수·연산 적용 컬럼 | 인덱스 컬럼에 함수 사용 시 풀 스캔 | 함수 사용 금지, 함수 기반 인덱스 검토 |
| 선두 컬럼 누락 | 복합 인덱스 선두 컬럼 없이 조회 | 쿼리 패턴 분석 후 컬럼 순서 조정 |
| UUID 기본 키 | 무작위 삽입으로 B+Tree 분할 빈발 | INT AUTO_INCREMENT 또는 시간 정렬 UUID 사용 |
| 낮은 카디널리티 인덱스 | 인덱스 효과 없이 오버헤드만 발생 | 해당 컬럼 인덱스 제거 검토 |
데이터베이스별 B+Tree 구현 현황
| 데이터베이스 | 기본 인덱스 구조 | 특이사항 |
|---|---|---|
| MySQL InnoDB | B+Tree | 클러스터드 인덱스 기본 적용 |
| MySQL MyISAM | B+Tree | 세컨더리 인덱스만 존재 |
| PostgreSQL | B+Tree | 기본값, Hash·GiST·GIN 등 선택 가능 |
| Oracle | B+Tree | 기본값, Bitmap 인덱스 선택 가능 |
| SQL Server | B+Tree | 클러스터드·논클러스터드 구분 |
| SQLite | B+Tree | 경량 B+Tree 구현 |
| MongoDB | B+Tree | WiredTiger 스토리지 엔진 사용 |
RDBMS의 사실상 표준 인덱스 구조가 B+Tree인 것을 확인할 수 있습니다.
기술 면접 단골 질문 – 핵심 요약
Q: B-Tree와 B+Tree의 가장 큰 차이는?
A: 데이터 저장 위치와 리프 노드 연결 여부.
B-Tree는 모든 노드에 데이터 저장.
B+Tree는 리프 노드에만 데이터 저장하고 리프끼리 연결 리스트로 연결.
Q: 데이터베이스 인덱스가 B+Tree를 선택한 이유는?
A: ① 리프 연결 리스트로 범위 탐색 효율적 처리
② 내부 노드에 키만 저장해 팬아웃 증가, 트리 높이 감소
③ 순차 탐색 최적화 → ORDER BY 성능 향상
④ 모든 데이터에 균등한 O(log N) 탐색 시간 보장
⑤ 내부 노드 캐시 효율 향상으로 디스크 I/O 최소화
Q: InnoDB 클러스터드 인덱스와 세컨더리 인덱스의 차이는?
A: 클러스터드 인덱스는 리프 노드에 실제 row 데이터 저장.
세컨더리 인덱스는 리프 노드에 기본 키 값만 저장 후 PK로 재조회.
결론
B+Tree 데이터베이스 인덱스가 선택된 이유는 명확합니다. 내부 노드에 키만 저장해 팬아웃을 극대화하고, 리프 노드를 연결 리스트로 연결해 범위 검색과 순차 탐색을 최적화하며, 모든 데이터에 균일한 O(log N) 탐색 시간을 보장하는 세 가지 설계 결정이 데이터베이스의 실제 워크로드(범위 검색, 정렬, 대용량 순차 스캔)와 정확히 맞아떨어지기 때문입니다. 인덱스는 마법이 아닙니다. B+Tree의 구조를 이해하고 나면 어떤 컬럼에, 어떤 순서로 인덱스를 걸어야 하는지 논리적으로 판단할 수 있습니다.
지금 바로 운영 중인 쿼리에 EXPLAIN을 실행해 인덱스가 제대로 활용되고 있는지 점검해 보세요.
답글 남기기