DB 인덱스 개념과 성능 비교 — 쉽게 이해하는 실전 가이드

인덱스는 데이터베이스 성능을 좌우합니다.
이 글에서는 인덱스의 기본 개념, 주요 타입, 비용(읽기/쓰기 트레이드오프),
성능 측정법, 실전 팁을 예시와 함께 정리합니다.
인덱스만큼 DB 성능을 끌어올릴 수 있는것도 없지만
잘못쓰면 쓰만도 못한 결과를 낼 수 있습니다.
- 인덱스는 검색 속도를 높이지만, 쓰기 비용과 디스크 사용량이 증가한다.
- 빈번한 범위 검색에는 B-Tree(또는 B+Tree)형이, 등가 검색(=)에는 해시 인덱스가 유리하다.
- 커버링 인덱스는 I/O를 크게 줄인다 — SELECT 절의 필요한 모든 컬럼을 인덱스에 포함시키자.
- 인덱스 설계는 선택도(selectivity)와 쿼리 패턴을 기반으로 해야 한다.
1. 인덱스란 무엇인가?
인덱스는 책의 목차와 같습니다. 데이터베이스 테이블의 특정 컬럼에 대해 빠른 검색을 가능하게 하는
추가 자료구조 입니다. 인덱스를 통해 전체 테이블 스캔(full table scan) 대신 일부 블록만 읽어 결과를 얻습니다.
주의: 인덱스는 항상 좋은 것이 아닙니다. 쓰기(INSERT/UPDATE/DELETE) 시 인덱스도 갱신되므로 비용이 발생합니다.
2. 주요 인덱스 타입과 특징
- B-Tree / B+Tree (범용) — 범위 검색(>, <, BETWEEN), 정렬(ORDER BY), 접두사 검색에 강함. 대부분의 RDBMS 기본 인덱스.
- 해시 인덱스 — 동등 비교(=)에 매우 빠르지만 범위 검색 불가. 메모리 기반 해시 또는 특정 엔진(MySQL MEMORY/Hash 등)에서 지원.
- 비트맵 인덱스 — 낮은 카디널리티(성별 등)에 효과적, 주로 데이터웨어하우스에서 사용. 병렬비트 연산에 유리하지만 OLTP 환경의 빈번한 쓰기에는 부적합.
- 함수/표현식 인덱스 (Functional / Expression) — 컬럼에 함수 적용 결과를 인덱싱해 WHERE LOWER(name) = 'foo' 같은 경우에 사용.
- 복합(다중) 인덱스 — 여러 컬럼을 묶어 인덱스 생성. (칼럼 순서가 중요)
- 클러스터드 인덱스(Clustered) — 테이블 데이터 자체가 인덱스 순서로 정렬됨 (ex: MySQL InnoDB의 PK). 한 테이블에 하나만 가능.
- 커버링 인덱스(Covering) — 쿼리에 필요한 모든 컬럼을 인덱스에 포함시켜 테이블 접근 없이 인덱스만으로 응답 가능.
3. 성능 비교
| 타입 | 강점 | 약점 | 추천 상황 |
|---|---|---|---|
| B-Tree | 범위검색·정렬에 탁월 | 높은 쓰기 비용(인덱스 유지) | 일반 OLTP/OLAP 모두 |
| Hash | 등가 조회(=) 초고속 | 범위·정렬 불가, 충돌관리 필요 | 캐시·세션·키-값 조회 |
| Bitmap | 저카디널리티 조건 조합 빠름 | 쓰기 부하 큰 환경 부적합 | DW/분석용 대규모 읽기 |
4. 복합 인덱스 설계 포인트
복합 인덱스는 왼쪽 접두사 규칙(prefix rule)이 적용됩니다. 예를 들어:
CREATE INDEX idx_user_a_b ON users (country, status, created_at);
- 효율적인 사용: WHERE country = 'KR' AND status = 'active' … → 인덱스 사용
- 비효율적인 사용: WHERE status = 'active' → 앞쪽 컬럼(country)을 사용하지 않으면 인덱스가 부분적으로만 활용될 수 있음
- ORDER BY와 GROUP BY에도 인덱스 순서가 영향을 줌
5. 커버링 인덱스 & 인덱스 온리 스캔
쿼리에서 SELECT에 필요한 모든 컬럼이 인덱스에 포함되면 DB는 테이블 레코드를 읽지 않고 인덱스만으로 응답할 수 있습니다(인덱스 온리 스캔). I/O가 줄어들어 속도가 크게 개선됩니다.
-- 예: users(name, email)을 자주 조회한다면
CREATE INDEX idx_users_name_email ON users (name, email);
-- SELECT name, email FROM users WHERE name LIKE 'Kim%'; -- 인덱스만으로 처리 가능
6. 인덱스의 비용 (쓰기·스토리지·유지관리)
- 쓰기 비용: INSERT/UPDATE/DELETE 시 인덱스 노드도 갱신 → 더 많은 디스크 I/O와 CPU
- 스토리지: 인덱스는 추가 디스크 사용. 넓은 커버링 인덱스는 용량 급증
- 단편화/통계: 인덱스가 분산되거나 통계가 오래되면 쿼리 플래너가 비효율적 선택을 함 → 정기적 REINDEX/ANALYZE 필요
운영 팁: 대량 로드(배치) 전에는 인덱스를 DROP 후 로드 완료 → 다시 생성하면 전체 비용이 줄어듭니다.
7. 성능 측정 방법 & EXPLAIN 활용
- EXPLAIN / EXPLAIN ANALYZE로 쿼리 플랜 확인 — 인덱스 사용 여부, 비용, 예상 행수 체크
- 실제 측정: 운영 환경 유사한 데이터/동시성으로 응답시간 측정 (단순 벤치마크는 오해 유발)
- 인덱스 스캔 통계: MySQL의 INFORMATION_SCHEMA, PostgreSQL의 pg_stat_user_indexes 등으로 사용 빈도 및 효율 체크
-- PostgreSQL 예시
EXPLAIN ANALYZE SELECT * FROM orders WHERE customer_id = 12345;
-- MySQL 예시
EXPLAIN SELECT id, total FROM orders WHERE created_at >= '2025-01-01';
8. 실전 인덱스 설계 체크리스트
- 쿼리 로그에서 가장 비용 큰 쿼리(응답시간·디스크 I/O)를 먼저 분석
- 높은 선택도(selectivity)가 예상되는 컬럼(예: 고유값)에 인덱스 우선 적용
- 복합 인덱스는 WHERE/ORDER BY의 공통 패턴을 반영해 순서 결정
- 커버링 인덱스를 활용해 디스크 I/O를 줄이되, 인덱스 크기 증가를 고려
- 자주 업데이트되는 컬럼에는 인덱스를 신중히 — 쓰기 성능 저하 가능
- 정기적으로 통계(ANALYZE)와 필요시 REINDEX/OPTIMIZE 수행
9. 간단 사례 — 실무 예시
문제: 사용자 목록 페이지가 느리다 — WHERE country='KR' AND status='active' ORDER BY last_login DESC
해결책: 복합 인덱스 (country, status, last_login DESC)을 생성하면 WHERE 필터 + ORDER BY를 동시에 만족해 정렬 비용과 I/O를 줄일 수 있음.
CREATE INDEX idx_users_country_status_lastlogin ON users (country, status, last_login DESC);
※ DB 엔진과 버전에 따라 DESC 인덱스 지원/활용 차이가 있으니 EXPLAIN으로 확인하세요.
마무리 — 항상 '측정'을 먼저 하세요
인덱스는 도구(tool)입니다. 막연히 많이 만들면 쓰기 성능과 저장비용만 커집니다. 쿼리 로그 분석 → EXPLAIN → 작은 인덱스 추가 → 재측정의 반복이 가장 안전한 접근법입니다.
팁: 운영 환경과 유사한 데이터셋으로 테스트하는 것이 반드시 필요합니다.
'개발 · IT > 백엔드' 카테고리의 다른 글
| 트랜잭션 ACID 쉽게 이해하기 — 핵심 개념 · 예시 · 실무 팁 (0) | 2025.11.17 |
|---|---|
| 조인(LEFT / INNER / RIGHT) 완전 정리 — 개념 · 예제 · 활용 팁 (0) | 2025.11.17 |
| 실무에서 자주 쓰는 SQL 튜닝 기법 성능 최적화 가이드 (0) | 2025.11.14 |
| Node.js 모듈 전쟁: CJS vs ESM, 지금 시작할 때 선택은? (0) | 2025.10.30 |
| 백엔드에서 성능 최적화를 위한 10가지 팁 (0) | 2024.10.14 |
댓글