본문 바로가기

Programming/Database

인덱스를 타지 않는 쿼리, 원인과 점검

추천캐릭터 2026. 7. 16. 08:00
728x90

인덱스를 타지 않는 쿼리, 원인과 점검

결론부터

인덱스를 걸었는데 쿼리가 느리다면, 십중팔구 옵티마이저가 인덱스를 안 쓰기로 결정한 것이다. 인덱스 존재와 인덱스 사용은 다르다. 원인은 대개 정해져 있다 — 인덱스 컬럼을 가공하거나, 타입이 어긋나거나, 복합 인덱스의 선두 컬럼을 빠뜨렸거나, 선택도가 낮은 경우다. 추측하기 전에 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가 있지만, 원인을 먼저 제거하는 게 우선이다. 강제는 임시방편이다.

마무리

'인덱스를 걸었는데 왜 느리지'의 답은 대부분 실행 계획 안에 있다. 컬럼을 가공하지 않고, 타입을 맞추고, 복합 인덱스는 선두 컬럼부터 쓰는 것 — 이 세 가지만 지켜도 인덱스가 안 타는 경우의 대부분이 사라진다.

728x90