데이터베이스 성능 튜닝의 80%는 인덱스(Index) 설계에서 결정됩니다. 인덱스는 책의 색인과 같아서, 방대한 데이터 속에서 원하는 정보를 빠르게 찾을 수 있게 돕습니다. 하지만 인덱스를 너무 많이 만들면 오히려 쓰기 성능이 떨어지는 부작용이 있죠. 관계형 DB의 핵심인 B-Tree 인덱스의 작동 원리와 실행 계획을 읽는 법을 정리해요
B-Tree 인덱스의 구조
대부분의 관계형 DB(MySQL, PostgreSQL 등)는 B-Tree(Balanced Tree) 자료구조를 인덱스에 사용합니다
flowchart TD
Root["Root Node<br/>(10, 20)"]
Internal1["Branch Node<br/>(1, 5)"]
Internal2["Branch Node<br/>(11, 15)"]
Internal3["Branch Node<br/>(21, 25)"]
Leaf1["Leaf Node<br/>(1, 2, 3)"]
Leaf2["Leaf Node<br/>(11, 12, 13)"]
Root --> Internal1
Root --> Internal2
Root --> Internal3
Internal1 --> Leaf1
Internal2 --> Leaf2
classDef primary fill:#2563eb,stroke:#1e40af,color:#ffffff
classDef success fill:#059669,stroke:#047857,color:#ffffff
class Root primary
class Internal1,Internal2,Internal3 success
- Root/Branch Node: 하위 노드로 가기 위한 가이드 역할을 합니다
- Leaf Node: 실제 데이터의 위치(주소) 또는 데이터 자체를 담고 있으며, 리프 노드끼리는 연결 리스트로 이어져 있어 범위 검색(Range Scan)에 유리합니다
클러스터형 vs 비클러스터형 인덱스
| 구분 | 클러스터형 (Clustered) | 비클러스터형 (Secondary) |
|---|---|---|
| 저장 방식 | 실제 데이터 행이 인덱스 순서로 저장됨 | 데이터의 주소값(또는 PK)만 가짐 |
| 개수 | 테이블당 단 1개 (보통 PK) | 여러 개 생성 가능 |
| 비유 | 사전 (단어 자체가 정렬됨) | 책의 뒷면 색인 (페이지 번호만 있음) |
실행 계획(EXPLAIN) 읽는 법
쿼리가 느리다면 가장 먼저 EXPLAIN 명령어를 통해 DB가 어떻게 데이터를 찾는지 확인해야 합니다
- Index Seek: 인덱스를 사용하여 특정 위치로 바로 찾아가는 효율적인 방식입니다
- Index Scan: 인덱스 전체를 훑는 방식입니다. 테이블 전체를 읽는(Full Table Scan) 것보다는 낫지만 데이터가 많으면 느려집니다
핵심 인사이트: 커버링 인덱스 (Covering Index)
인덱스만 보고 쿼리 결과를 바로 반환할 수 있다면, 실제 데이터 블록을 읽으러 가는 디스크 I/O를 생략할 수 있습니다. 이를 커버링 인덱스라고 하며, 조회 성능을 극대화하는 가장 강력한 기법 중 하나입니다
정리
- 인덱스는 조회를 빠르게 하지만, CUD(추가/수정/삭제) 시 오버헤드가 발생합니다
- B-Tree 구조는 균형 잡힌 트리로 일정한 검색 성능을 보장합니다
- 실행 계획을 통해 인덱스가 의도대로 작동하는지 확인하는 습관이 중요합니다
- 불필요한 인덱스는 과감히 제거하고, 자주 쓰이는 검색 조건에 맞게 인덱스를 설계하세요
다음 글에서는 데이터의 정합성을 보장하는 트랜잭션과 격리 수준에 대해 알아봐요