인덱스를 만들었는데 안 타는 경우 — 옵티마이저가 무시하는 조건들
인덱스가 있는데 풀스캔이 나오는 대표적 원인 여섯 가지와 확인 방법.
먼저 실행 계획을 본다
"인덱스를 만들었는데 느리다"는 상황에서 가장 먼저 할 일은 추측이 아니라 확인이다.
```sql
EXPLAIN ANALYZE
SELECT * FROM orders WHERE created_at >= '2026-08-01';
```
EXPLAIN은 예상 계획, EXPLAIN ANALYZE는 실제 실행 결과다. 예상 행 수와 실제 행 수가 크게 다르면 통계가 낡은 것이므로 그것부터 갱신한다.
원인 1 — 컬럼에 함수를 씌움
가장 흔하다.
```sql
-- 인덱스 못 씀
WHERE DATE(created_at) = '2026-08-01'
WHERE UPPER(email) = 'A@B.COM'
-- 인덱스 사용 가능
WHERE created_at >= '2026-08-01' AND created_at < '2026-08-02'
WHERE email = 'a@b.com'
```
인덱스는 컬럼의 원본 값으로 정렬돼 있다. 함수를 거치면 그 값이 아니게 되므로 정렬을 활용할 수 없다.
함수를 꼭 써야 한다면 함수 기반 인덱스를 별도로 만드는 방법이 있다.
원인 2 — 타입 불일치
```sql
-- user_id가 VARCHAR인데 숫자로 비교
WHERE user_id = 12345
```
암묵적 형변환이 일어나면서 인덱스를 못 쓰는 경우가 있다. 파라미터 타입을 컬럼 타입에 맞춘다.
원인 3 — 선택도가 낮음
전체의 상당 비율을 가져오는 조건이면 인덱스를 타는 것이 오히려 느리다. 인덱스로 위치를 찾고 다시 테이블을 읽는 왕복이 발생하기 때문이다.
```sql
-- status가 두 종류뿐이고 대부분 'active'라면
WHERE status = 'active' -- 풀스캔이 더 빠를 수 있음
```
이 경우는 옵티마이저의 판단이 옳다. 인덱스를 강제할 것이 아니라 조건을 좁히거나 부분 인덱스를 고려한다.
원인 4 — 복합 인덱스의 순서
인덱스 (a, b, c)가 있을 때:
| 조건 | 활용 |
|---|---|
| a = ? | 사용 |
| a = ? AND b = ? | 사용 |
| a = ? AND c = ? | a까지만 사용 |
| b = ? | 대체로 못 씀 |
| b = ? AND c = ? | 대체로 못 씀 |
선행 컬럼이 조건에 있어야 한다. 자주 쓰는 조건 조합을 보고 순서를 정한다. 등호 조건 컬럼을 앞에, 범위 조건 컬럼을 뒤에 두는 것이 일반적인 지침이다.
원인 5 — OR 조건
```sql
WHERE a = 1 OR b = 2
```
각각에 인덱스가 있어도 OR로 묶이면 활용이 어려운 경우가 있다. UNION으로 분리하면 각 인덱스를 쓸 수 있다.
```sql
SELECT * FROM t WHERE a = 1
UNION
SELECT * FROM t WHERE b = 2
```
원인 6 — 앞이 열린 LIKE
```sql
WHERE name LIKE '%김%' -- 못 씀
WHERE name LIKE '김%' -- 사용 가능
```
인덱스는 앞에서부터 정렬돼 있으므로, 앞이 와일드카드면 시작 지점을 찾을 수 없다. 앞뒤 모두 열린 검색이 필요하면 전문 검색 인덱스를 고려한다.
확인 순서
EXPLAIN ANALYZE로 실제 계획 확인- 예상 행 수와 실제 행 수 비교 → 다르면 통계 갱신
- 위 여섯 원인 중 해당하는 것이 있는지 확인
- 인덱스 정의와 조건절의 컬럼 순서 대조
인덱스를 추가하기 전에 이 순서를 밟으면, 안 쓰이는 인덱스를 늘리는 것을 피할 수 있다. 인덱스는 조회를 돕지만 쓰기 비용과 저장 공간을 늘린다.
최종 수정 2026-08-28