인덱스를 타지 않는 쿼리, 원인과 점검
결론부터
인덱스를 걸었는데 쿼리가 느리다면, 십중팔구 옵티마이저가 인덱스를 안 쓰기로 결정한 것이다. 인덱스 존재와 인덱스 사용은 다르다. 원인은 대개 정해져 있다 — 인덱스 컬럼을 가공하거나, 타입이 어긋나거나, 복합 인덱스의 선두 컬럼을 빠뜨렸거나, 선택도가 낮은 경우다. 추측하기 전에 EXPLAIN으로 실제 실행 계획부터 확인하는 게 순서다.
확인: EXPLAIN부터
EXPLAIN
SELECT * FROM orders WHERE customer_id = 1001;
type 컬럼이 ALL이면 풀 테이블 스캔, ref·range·const면 인덱스를 탄 것이다. key 컬럼에서 실제로 어떤 인덱스가 선택됐는지도 함께 본다. 여기서 ALL이 보이면 아래 원인들을 하나씩 대조한다.
자주 걸리는 원인들
1) 인덱스 컬럼을 가공했다. 컬럼에 함수를 씌우면 인덱스가 무력화된다.
-- 인덱스 못 탐: created_at에 함수 적용
WHERE DATE(created_at) = '2026-07-13'
-- 인덱스 탐: 범위 조건으로 변환
WHERE created_at >= '2026-07-13' AND created_at < '2026-07-14'
2) 타입이 어긋난다. 문자열 컬럼을 숫자와 비교하면 암묵적 형변환이 일어나 인덱스를 못 탄다.
-- phone이 VARCHAR인데 숫자로 비교 → 형변환 발생
WHERE phone = 1012345678
-- 올바르게
WHERE phone = '1012345678'
3) 복합 인덱스의 선두 컬럼을 뺐다. (a, b, c) 인덱스는 a부터 시작해야 쓸 수 있다. 이를 leftmost prefix(선두 접두) 규칙이라 한다.
-- 인덱스 (customer_id, status) 기준
WHERE status = 'PAID' -- 선두(customer_id) 없음 → 못 탐
WHERE customer_id = 1001 AND status = 'PAID' -- 탐
4) 선두 와일드카드 LIKE. LIKE '%kim'처럼 앞에 %가 오면 인덱스를 못 탄다. LIKE 'kim%'는 탄다.
5) 선택도가 낮다. 값의 종류가 적으면(예: 성별) 옵티마이저가 풀스캔이 더 빠르다고 판단해 인덱스를 버린다. 이건 오히려 정상 동작이다.
실무 팁
- 통계가 오래되면 옵티마이저가 오판한다.
ANALYZE TABLE orders;로 통계를 갱신한다. SELECT *대신 필요한 컬럼만 조회하면, 인덱스만으로 조회를 끝내는 커버링 인덱스가 가능해진다.- 인덱스를 강제하는
FORCE INDEX가 있지만, 원인을 먼저 제거하는 게 우선이다. 강제는 임시방편이다.
마무리
'인덱스를 걸었는데 왜 느리지'의 답은 대부분 실행 계획 안에 있다. 컬럼을 가공하지 않고, 타입을 맞추고, 복합 인덱스는 선두 컬럼부터 쓰는 것 — 이 세 가지만 지켜도 인덱스가 안 타는 경우의 대부분이 사라진다.
'Programming > Database' 카테고리의 다른 글
| OFFSET 페이지네이션이 느려지는 이유: MariaDB 커서 방식과 Spring Data JPA 구현 (0) | 2026.07.29 |
|---|---|
| 낙관적 락 vs 비관적 락, 재고 차감에서 뭘 써야 하나 (0) | 2026.07.23 |
| Flyway로 끝내는 Spring Boot DB 마이그레이션 실전 가이드 (0) | 2026.07.11 |
| MariaDB vs MySQL 차이와 선택 기준 (0) | 2026.07.10 |
| MariaDB 버전 선택 가이드 (0) | 2026.07.08 |