SQL 인덱스(Index)란 무엇이며 언제 사용해야 하나요?
답변 포인트
조회 성능 향상과 쓰기 성능 저하의 트레이드오프를 생각해보세요.
정답 및 해설
빠른 요약
인덱스는 데이터 조회 속도를 높이기 위한 자료구조(B-Tree, Hash 등)입니다. 인덱스 없이는 Full Table Scan O(n), 인덱스 사용 시 O(log n).
인덱스(Index)는 데이터베이스 테이블의 특정 컬럼에 대한 검색 속도를 높이기 위한 자료구조입니다. 책의 목차나 색인처럼, 원하는 데이터가 어디에 있는지 빠르게 찾을 수 있도록 별도의 탐색 구조를 미리 만들어 두는 방식입니다.
인덱스가 없을 때 vs 있을 때
인덱스 없이 조회하면 데이터베이스는 Full Table Scan을 수행합니다. 테이블의 모든 행을 처음부터 끝까지 읽어야 하므로 시간 복잡도는 O(n) 입니다. 데이터가 100만 건이라면 최대 100만 번 읽어야 합니다.
인덱스를 사용하면 B-Tree 탐색을 통해 O(log n) 으로 줄어듭니다. 100만 건의 데이터라면 약 20번의 비교로 원하는 데이터를 찾을 수 있습니다.
-- 인덱스가 없는 경우: 100만 행 Full Scan
SELECT * FROM users WHERE email = 'user@example.com';
-- email 컬럼에 인덱스가 있는 경우: B-Tree 탐색 O(log n)
CREATE INDEX idx_users_email ON users(email);
SELECT * FROM users WHERE email = 'user@example.com';인덱스의 내부 구조
B-Tree 인덱스 (기본 인덱스)
대부분의 RDBMS(MySQL, PostgreSQL, Oracle 등)에서 기본으로 사용하는 구조입니다. 모든 리프 노드가 같은 깊이를 가지는 균형 트리(Balanced Tree)로, 범위 검색(BETWEEN, >, <)과 정렬(ORDER BY)에 효과적입니다.
[50]
/ \
[25] [75]
/ \ / \
[10] [30] [60] [90]WHERE age > 30같은 범위 쿼리에 적합ORDER BY절의 정렬 비용을 줄여줌LIKE 'abc%'처럼 앞부분이 고정된 패턴 검색에 유리
Hash 인덱스
해시 함수를 이용해 키를 버킷에 매핑하는 구조입니다. 정확한 일치(=) 검색에서 O(1) 의 속도를 낼 수 있지만, 범위 검색이나 정렬에는 사용할 수 없습니다.
| 특징 | B-Tree | Hash |
|---|---|---|
등호 검색 (=) | O(log n) | O(1) |
범위 검색 (>, <, BETWEEN) | 가능 | 불가능 |
정렬 (ORDER BY) | 가능 | 불가능 |
| 주요 사용처 | 일반 목적 | 메모리 DB, 캐싱 |
기타 인덱스 유형
- Composite Index (복합 인덱스): 두 개 이상의 컬럼을 묶어 하나의 인덱스로 생성
- Covering Index: 쿼리에 필요한 모든 컬럼이 인덱스에 포함되어 테이블 접근 없이 인덱스만으로 쿼리 처리
- Unique Index: 중복 값을 허용하지 않는 인덱스 (PK에 자동 생성)
- Full-Text Index: 텍스트 전문 검색을 위한 인덱스
인덱스를 사용해야 하는 상황
WHERE 절에 자주 등장하는 컬럼
-- users 테이블에서 특정 이메일 조회가 자주 발생
SELECT id, name FROM users WHERE email = ?;
-- email 컬럼에 인덱스 생성
CREATE INDEX idx_users_email ON users(email);JOIN 조건에 사용되는 컬럼
-- orders.user_id로 JOIN이 자주 발생
SELECT u.name, o.total
FROM users u
JOIN orders o ON u.id = o.user_id;
-- orders 테이블의 user_id에 인덱스 생성
CREATE INDEX idx_orders_user_id ON orders(user_id);ORDER BY / GROUP BY 컬럼
-- created_at 기준 정렬이 자주 발생
SELECT * FROM posts ORDER BY created_at DESC;
CREATE INDEX idx_posts_created_at ON posts(created_at);Cardinality(카디널리티)가 높은 컬럼
카디널리티란 컬럼에 있는 고유한 값의 수를 의미합니다. 인덱스는 카디널리티가 높은 컬럼(예: 이메일, 주민번호)에 효과적이며, 낮은 컬럼(예: 성별 M/F, boolean true/false)에는 효과가 거의 없습니다.
-- 카디널리티가 높음 → 인덱스 효과적
CREATE INDEX idx_high ON users(email); -- 유니크한 값 많음
-- 카디널리티가 낮음 → 인덱스 효과 미미
-- gender는 'M', 'F' 두 가지 값만 있어 50% 행을 읽어야 함
-- CREATE INDEX idx_low ON users(gender); -- 비효율적복합 인덱스 활용
여러 컬럼을 함께 검색하는 패턴이 있다면 복합 인덱스가 유리합니다.
-- 자주 사용하는 쿼리 패턴
SELECT * FROM orders WHERE user_id = 1 AND status = 'completed';
-- 복합 인덱스 생성 (순서가 중요!)
CREATE INDEX idx_orders_user_status ON orders(user_id, status);복합 인덱스는 왼쪽 접두어(Leftmost Prefix) 규칙을 따릅니다.
-- (user_id, status) 복합 인덱스가 있을 때:
WHERE user_id = 1 -- 인덱스 사용 가능 (왼쪽 컬럼만)
WHERE user_id = 1 AND status = 'completed' -- 인덱스 사용 가능 (두 컬럼 모두)
WHERE status = 'completed' -- 인덱스 사용 불가 (왼쪽 컬럼 누락)인덱스의 단점
1. 추가 저장 공간 필요
인덱스는 별도의 자료구조로 저장되므로 디스크 공간을 추가로 소모합니다. 인덱스가 많을수록 저장 공간도 증가합니다.
2. 쓰기 성능 저하 (INSERT / UPDATE / DELETE)
데이터가 변경될 때마다 관련된 모든 인덱스도 함께 갱신해야 합니다. 인덱스가 많을수록 쓰기 작업의 오버헤드가 커집니다.
인덱스 1개 → 데이터 삽입 시 B-Tree 1번 갱신
인덱스 5개 → 데이터 삽입 시 B-Tree 5번 갱신3. 인덱스가 무시되는 경우
인덱스를 만들었더라도 다음과 같은 경우에는 옵티마이저가 인덱스를 사용하지 않을 수 있습니다.
-- 함수로 감싼 컬럼 → 인덱스 무시됨
WHERE YEAR(created_at) = 2024 -- 비효율
WHERE created_at >= '2024-01-01' -- 효율적
-- 앞에 와일드카드 LIKE → 인덱스 무시됨
WHERE name LIKE '%홍길동' -- Full Scan
WHERE name LIKE '홍길동%' -- 인덱스 사용 가능
-- 묵시적 형 변환 → 인덱스 무시됨
WHERE user_id = '123' -- user_id가 INT이면 형변환 발생
WHERE user_id = 123 -- 인덱스 정상 사용인덱스 생성 및 관리 명령어
-- 인덱스 생성
CREATE INDEX idx_name ON table_name(column_name);
-- 유니크 인덱스 생성
CREATE UNIQUE INDEX idx_unique ON users(email);
-- 복합 인덱스 생성
CREATE INDEX idx_composite ON orders(user_id, status, created_at);
-- 인덱스 삭제
DROP INDEX idx_name ON table_name;
-- 인덱스 목록 확인 (MySQL)
SHOW INDEX FROM table_name;
-- 쿼리 실행 계획 확인 (인덱스 사용 여부 확인)
EXPLAIN SELECT * FROM users WHERE email = 'test@example.com';인덱스 사용 전략 요약
| 상황 | 권장 여부 | 이유 |
|---|---|---|
| WHERE 조건에 자주 쓰이는 컬럼 | 권장 | 검색 속도 향상 |
| JOIN의 연결 컬럼 | 권장 | 조인 비용 감소 |
| 카디널리티가 높은 컬럼 | 권장 | 필터링 효과 큼 |
| 카디널리티가 낮은 컬럼 | 비권장 | 효과 미미, 오버헤드만 발생 |
| INSERT/UPDATE가 매우 빈번한 테이블 | 신중 | 쓰기 성능 저하 |
| 소규모 테이블 | 비권장 | Full Scan이 더 빠를 수 있음 |