SQL 인덱스 원리와 튜닝: 느린 쿼리를 빠르게 만드는 법
"인덱스 걸었는데 왜 안 빨라지죠?" 실무에서 정말 자주 나오는 질문입니다. 인덱스는 마법이 아니라 **정렬된 색인(목차)**입니다. 책 뒤 찾아보기처럼, 원리를 알아야 제대로 씁니다. 대부분의 RDB 인덱스는 B-Tree 구조로, 값이 정렬되어 있어 범위 검색과 정렬에 강합니다.
1. 인덱스는 언제 효과가 있나
-- email로 자주 조회한다면
CREATE INDEX idx_users_email ON users (email);
-- 실행 계획 확인 (인덱스 타는지 확인 필수)
EXPLAIN SELECT * FROM users WHERE email = 'a@b.com';
핵심: EXPLAIN으로 실제 인덱스를 타는지(type: ref 등) 반드시 확인하세요. 안 보고 추측하면 안 됩니다.
2. 복합 인덱스와 좌측 우선 규칙
(a, b, c) 순서로 만든 인덱스는 왼쪽부터 연속으로만 활용됩니다.
| WHERE 조건 | 인덱스 사용 |
|---|---|
a = ? | O |
a = ? AND b = ? | O |
b = ? 단독 | X (좌측 a 건너뜀) |
a = ? AND c = ? | a까지만 |
CREATE INDEX idx_orders ON orders (user_id, status, created_at);
-- 잘 탐: WHERE user_id = 5 AND status = 'PAID'
-- 못 탐: WHERE status = 'PAID' (user_id 없이 status만)
컬럼 순서 규칙: 등호(=) 조건 컬럼을 앞에, 범위(>, <) 조건은 뒤에 두고, 같은 등호끼리는 카디널리티(중복 적은 값)가 높은 컬럼을 앞에 둡니다.
3. 인덱스를 무력화하는 안티패턴
-- 나쁨: 컬럼을 가공하면 인덱스 못 씀
SELECT * FROM users WHERE YEAR(created_at) = 2026;
-- 좋음: 컬럼은 그대로, 값 쪽을 가공
SELECT * FROM users
WHERE created_at >= '2026-01-01' AND created_at < '2027-01-01';
주의: LIKE '%검색%'(앞에 와일드카드), OR 남발, 컬럼에 함수/연산 적용은 인덱스를 무력화합니다.
4. 커버링 인덱스로 한 단계 더
쿼리가 필요로 하는 컬럼을 인덱스에 모두 포함하면, 테이블 접근 없이 인덱스만 읽고 끝(index-only scan)나서 가장 빠릅니다.
-- user_id로 조회해 status, created_at만 본다면
CREATE INDEX idx_cover ON orders (user_id, status, created_at);
SELECT status, created_at FROM orders WHERE user_id = 5;
마무리 체크리스트
- 튜닝 전후
EXPLAIN으로 검증 - 복합 인덱스는 좌측 우선, 등호→범위 순서
- WHERE 컬럼에 함수/연산 금지
- 선두
%LIKE 피하기 - 인덱스도 쓰기 비용이 있으니 무분별하게 늘리지 말 것
함께 보면 좋은 글
REST API 설계 원칙: 실무에서 욕먹지 않는 7가지 규칙
URL에 동사를 넣고, 200으로 에러를 내보내는 잘못된 API는 이제 그만. 리소스 명명, HTTP 메서드, 상태 코드, 버저닝까지 실무 표준에 맞춰 REST API를 설계하는 법을 정리했습니다.
Docker 입문: 이미지와 컨테이너 개념부터 첫 실행까지
이미지와 컨테이너의 차이가 헷갈리는 입문자를 위한 가이드입니다. Dockerfile 작성, build/run/exec 명령어, 그리고 실무에서 자주 쓰는 명령을 실제 예제로 익혀보세요.
자주 쓰는 Git 명령어 정리: 실무 필수 25개 한 장 요약
add, commit, branch, rebase, stash까지 실무에서 매일 쓰는 Git 명령어를 시나리오별로 정리했습니다. 잘못 커밋했을 때 되돌리는 방법과 협업 충돌 해결 흐름까지 한 번에 익혀보세요.