MySQL 인덱스 최적화 완벽 가이드: 쿼리 성능 10배 향상시키기

느린 쿼리를 빠르게 만드는 MySQL 인덱스 최적화 실전 가이드. 단일 인덱스, 복합 인덱스, 커버링 인덱스까지 실제 예제로 배웁니다.

럿지 AI 팀
4분 읽기

MySQL 인덱스 완벽 가이드



인덱스란?



책의 색인처럼 데이터를 빠르게 찾기 위한 자료구조

**효과:** 쿼리 속도 10-100배 향상

1단계: 문제 확인



느린 쿼리 예제



``sql
-- 회원 100만 명 중 이메일로 검색
SELECT * FROM members WHERE email = 'user@example.com';
-- 실행 시간: 3초
`

EXPLAIN으로 분석



`sql
EXPLAIN SELECT * FROM members WHERE email = 'user@example.com';

-- 결과:
-- type: ALL (풀 테이블 스캔!)
-- rows: 1000000 (100만 행 모두 읽음)
`

**문제:** 인덱스 없어서 전체 테이블 스캔

2단계: 단일 인덱스



인덱스 생성



`sql
CREATE INDEX idx_email ON members(email);
`

효과 확인



`sql
EXPLAIN SELECT * FROM members WHERE email = 'user@example.com';

-- 결과:
-- type: ref (인덱스 사용!)
-- rows: 1
`

**실행 시간:** 3초 → **0.01초** (300배 향상!)

3단계: 복합 인덱스



복합 쿼리



`sql
-- 상태와 날짜로 주문 검색
SELECT * FROM orders
WHERE status = 'PAID' AND order_date >= '2024-01-01';
-- 느림!
`

잘못된 인덱스



`sql
-- 각각 단일 인덱스 생성 (비효율)
CREATE INDEX idx_status ON orders(status);
CREATE INDEX idx_date ON orders(order_date);
`

**문제:** MySQL은 하나만 사용

올바른 복합 인덱스



`sql
CREATE INDEX idx_status_date ON orders(status, order_date);
`

컬럼 순서 중요!



**원칙:** 선택도가 높은 컬럼을 앞에

`sql
-- 좋음: status(5종류) → order_date(다양)
CREATE INDEX idx_status_date ON orders(status, order_date);

-- 나쁨: 순서 반대
CREATE INDEX idx_date_status ON orders(order_date, status);
`

**효과:**
- 좋은 순서: 0.05초
- 나쁜 순서: 0.5초

4단계: 커버링 인덱스



일반 쿼리



`sql
SELECT id, email, name FROM members WHERE email = 'user@example.com';

-- 과정:
-- 1. idx_email에서 id 찾기
-- 2. 테이블에서 name 가져오기 (추가 I/O)
`

커버링 인덱스



`sql
CREATE INDEX idx_email_name ON members(email, name);

-- 이제 인덱스만으로 모든 데이터 제공!
`

**효과:**
- Before: 0.01초
- After: **0.002초** (5배 향상)

5단계: WHERE 절 패턴별 인덱스



등호(=)



`sql
WHERE status = 'PAID'
-- 인덱스: CREATE INDEX idx_status ON orders(status);
`

범위(>, <, BETWEEN)



`sql
WHERE order_date >= '2024-01-01'
-- 인덱스: CREATE INDEX idx_date ON orders(order_date);
`

LIKE (앞부분 일치)



`sql
WHERE name LIKE '홍%' -- ✓ 인덱스 사용
WHERE name LIKE '%홍%' -- ✗ 인덱스 미사용
`

IN



`sql
WHERE status IN ('PAID', 'SHIPPED')
-- 인덱스: CREATE INDEX idx_status ON orders(status);
`

6단계: 인덱스 확인



현재 인덱스 보기



`sql
SHOW INDEX FROM orders;
`

실행 계획 분석



`sql
EXPLAIN SELECT * FROM orders
WHERE status = 'PAID' AND order_date >= '2024-01-01';

-- 확인 사항:
-- type: ref (좋음) vs ALL (나쁨)
-- key: 사용된 인덱스 이름
-- rows: 검사한 행 수 (적을수록 좋음)
`

7단계: 불필요한 인덱스 제거



중복 인덱스



`sql
-- 중복!
CREATE INDEX idx_email ON members(email);
CREATE INDEX idx_email_name ON members(email, name);

-- idx_email은 불필요 (idx_email_name이 커버)
DROP INDEX idx_email ON members;
`

사용 안 하는 인덱스



`sql
-- 사용 통계 확인 (MySQL 8.0+)
SELECT * FROM sys.schema_unused_indexes;

-- 사용 안 하는 인덱스 삭제
DROP INDEX idx_old ON members;
`

**이유:** 인덱스는 쓰기 성능 저하

실전 예제



Case 1: 주문 검색



`sql
-- 쿼리
SELECT order_id, total_amount
FROM orders
WHERE member_id = 123 AND status = 'PAID'
ORDER BY order_date DESC
LIMIT 10;

-- 최적 인덱스
CREATE INDEX idx_member_status_date
ON orders(member_id, status, order_date DESC);

-- 효과: 2초 → 0.01초
`

Case 2: 상품 검색



`sql
-- 쿼리
SELECT product_id, name, price
FROM products
WHERE category_id = 5 AND price BETWEEN 10000 AND 50000;

-- 최적 인덱스
CREATE INDEX idx_category_price
ON products(category_id, price);

-- 커버링 인덱스 (name 포함)
CREATE INDEX idx_category_price_name
ON products(category_id, price, name);

-- 효과: 1초 → 0.005초
`

주의사항



1. 인덱스 남발 금지



**문제:**
- 쓰기 성능 저하
- 스토리지 낭비

**원칙:**
자주 조회되는 WHERE/ORDER BY 컬럼만

2. 함수 사용 시 인덱스 무효화



`sql
-- ✗ 인덱스 사용 안 됨
WHERE YEAR(order_date) = 2024

-- ✓ 인덱스 사용됨
WHERE order_date >= '2024-01-01' AND order_date < '2025-01-01'
`

3. NULL 값



`sql
-- NULL 체크는 인덱스 사용 가능
WHERE email IS NOT NULL
`

성능 측정



Before



`sql
-- 인덱스 없음
SELECT * FROM orders WHERE member_id = 123;
-- 실행 시간: 5초
-- 검사 행: 1,000,000
`

After



`sql
-- 인덱스 있음
CREATE INDEX idx_member ON orders(member_id);
SELECT * FROM orders WHERE member_id = 123;
-- 실행 시간: 0.01초
-- 검사 행: 50
``

**개선:** 500배!

더 배우기



김영한의 실전 데이터베이스
- 파티셔닝과 인덱스
- 대용량 데이터 최적화
- 실전 프로젝트

---

**태그**: #MySQL인덱스 #쿼리최적화 #성능튜닝 #복합인덱스 #튜토리얼

L

럿지 AI 팀

AI 기술과 비즈니스 혁신을 선도하는 럿지 AI의 콘텐츠 팀입니다.