B+Tree 데이터베이스 인덱스 정리 – B-Tree와 B+Tree 차이, 선택 이유까지 한 번에


B+Tree 데이터베이스 인덱스가 왜 B-Tree 대신 선택됐는지 궁금하셨나요? “MySQL 인덱스가 B+Tree 구조라는 건 알겠는데, 왜 굳이 B-Tree가 아닌 B+Tree여야 하나요?” 기술 면접에서도 단골로 등장하고, 실무에서도 인덱스를 올바르게 설계하려면 반드시 이해해야 하는 개념입니다. 이 글에서는 이진 탐색 트리의 한계에서 출발해 B-Tree의 등장 배경, B-Tree와 B+Tree의 구조적 차이, 그리고 데이터베이스가 B+Tree를 선택한 결정적 이유까지 그림과 예시로 단계별로 풀어드립니다. 끝까지 읽으면 인덱스 구조의 전체 그림이 선명하게 잡힐 것입니다.


목차

  1. 트리 자료구조 기초 – 이진 탐색 트리의 한계까지
  2. B-Tree의 등장 – 디스크 I/O를 줄이기 위한 핵심 원리
  3. B-Tree와 B+Tree의 구조적 차이 완벽 비교
  4. B+Tree가 데이터베이스 인덱스에 최적인 결정적 이유
  5. 실무에서 B+Tree 인덱스가 작동하는 방식 – MySQL InnoDB 중심
  6. 전문가 관점 – 인덱스 설계 원칙과 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-TreeB+Tree
데이터 저장 위치모든 노드(내부+리프)리프 노드에만 저장
내부 노드 역할탐색 + 데이터 저장탐색(인덱스)만 담당
리프 노드 연결연결 없음연결 리스트로 순서 연결
내부 노드 용량키 + 데이터로 노드 크기 큼키만 저장하여 노드 크기 작음
같은 페이지에 저장 가능한 키 수적음많음 (팬아웃 높음)
트리 높이상대적으로 높음상대적으로 낮음
특정값 탐색내부 노드에서도 찾을 수 있어 빠를 수 있음항상 리프까지 내려가야 함
범위 탐색트리를 다시 순회해야 함리프 연결 리스트로 선형 탐색 가능
순차 탐색비효율적매우 효율적
주요 사용처파일 시스템 일부RDBMS 인덱스 (MySQL, Oracle, PostgreSQL)

탐색 경로 비교 시뮬레이션

40을 찾는 경우를 비교해 봅니다.

[B-Tree에서 40 탐색]

루트 [30,data | 70,data]
  └─ 30 &lt; 40 &lt; 70 → 가운데 자식으로
      [40,data | 60,data]
        └─ 40 발견! data 즉시 반환
           → 2번 접근으로 완료 ✅

[B+Tree에서 40 탐색]

루트 [30 | 70]
  └─ 30 &lt; 40 &lt; 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 &lt;= 50000;
SELECT * FROM users WHERE age >= 20 AND age &lt;= 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 InnoDBB+Tree클러스터드 인덱스 기본 적용
MySQL MyISAMB+Tree세컨더리 인덱스만 존재
PostgreSQLB+Tree기본값, Hash·GiST·GIN 등 선택 가능
OracleB+Tree기본값, Bitmap 인덱스 선택 가능
SQL ServerB+Tree클러스터드·논클러스터드 구분
SQLiteB+Tree경량 B+Tree 구현
MongoDBB+TreeWiredTiger 스토리지 엔진 사용

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을 실행해 인덱스가 제대로 활용되고 있는지 점검해 보세요.

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다