Database · 2025-09-22 · 2분 읽기

MySQL 인덱스 제대로 이해하기 - 왜 느린지 EXPLAIN으로 파악하기

목차
  • 1. B-Tree 인덱스 구조
  • 2. EXPLAIN 읽기
  • 3. 복합 인덱스와 선두 컬럼
  • 4. 인덱스가 무시되는 경우
  • 5. 커버링 인덱스

1. B-Tree 인덱스 구조

MySQL InnoDB의 기본 인덱스는 B-Tree다. 루트 → 브랜치 → 리프 순으로 탐색한다.

리프 노드에는 실제 데이터(클러스터드 인덱스) 또는 PK 값(세컨더리 인덱스)이 저장된다.

-- 풀 테이블 스캔: 1000만 건 전체 읽음
SELECT * FROM orders WHERE status = 'PENDING';
 
-- 인덱스 추가 후: B-Tree 탐색으로 해당 건만 읽음
CREATE INDEX idx_orders_status ON orders(status);

2. EXPLAIN 읽기

EXPLAIN SELECT * FROM orders
WHERE user_id = 1 AND status = 'PENDING'
ORDER BY created_at DESC
LIMIT 10;
컬럼주목할 값
typeALL (풀스캔) → ref, range 로 개선
key실제 사용된 인덱스
rows예상 조회 행 수 (적을수록 좋음)
ExtraUsing filesort, Using temporary 는 위험 신호

3. 복합 인덱스와 선두 컬럼

복합 인덱스는 선두 컬럼부터 순서대로 사용된다.

CREATE INDEX idx_user_status_date ON orders(user_id, status, created_at);
 
-- 사용됨 ✓
WHERE user_id = 1
WHERE user_id = 1 AND status = 'PENDING'
WHERE user_id = 1 AND status = 'PENDING' AND created_at > '2025-01-01'
 
-- 사용 안 됨 ✗
WHERE status = 'PENDING'
WHERE created_at > '2025-01-01'

4. 인덱스가 무시되는 경우

-- 함수 사용
WHERE YEAR(created_at) = 2025   -- ✗
WHERE created_at >= '2025-01-01' AND created_at < '2026-01-01'  -- ✓
 
-- 타입 불일치
WHERE user_id = '1'  -- user_id가 INT인데 문자열 비교 → ✗
 
-- LIKE 앞쪽 와일드카드
WHERE name LIKE '%홍길동'  -- ✗
WHERE name LIKE '홍길동%'  -- ✓
 
-- OR 조건 (두 컬럼에 각각 인덱스가 있어야 함)
WHERE user_id = 1 OR status = 'PENDING'

5. 커버링 인덱스

인덱스만으로 쿼리를 처리할 수 있으면 테이블 접근 없이 결과 반환이 가능하다.

CREATE INDEX idx_covering ON orders(user_id, status, id, amount);
 
-- Extra: Using index (테이블 접근 없음)
SELECT id, amount FROM orders WHERE user_id = 1 AND status = 'PAID';

조회 성능이 크게 개선된다. 단, 인덱스 크기가 커지므로 쓰기 성능과 트레이드오프.

'Database ' 카테고리의 다른 글

  • 이 카테고리에 다른 글이 없습니다.