느린 쿼리 개선 실전: 인덱스 추가 전후 비교
느린 쿼리 로그에서 범인을 찾았다면 이제 고칠 차례다. 이 글은 실제로 자주 만나는 형태의 쿼리 하나를 예로, 실행 계획 확인부터 인덱스 추가, 전후 비교까지 느린 쿼리 개선 과정을 순서대로 보여 준다. 느린 쿼리를 기록하고 찾는 방법은 MySQL 느린 쿼리 로그에 먼저 정리해 두었다.

문제의 쿼리
-- 주문 50만 건 테이블에서 특정 고객의 최근 주문 20건
SELECT id, amount, ordered_at
FROM orders
WHERE customer = 'kim'
ORDER BY ordered_at DESC
LIMIT 20;
-- 1.8초
느린 쿼리 개선 1단계 실행 계획 보기
EXPLAIN SELECT id, amount, ordered_at FROM orders
WHERE customer = 'kim' ORDER BY ordered_at DESC LIMIT 20;
-- type: ALL, key: NULL, rows: 498213, Extra: Using where; Using filesort
- type ALL: 테이블 전체를 읽는다.
- key NULL: 쓰는 인덱스가 없다.
- rows: 약 50만 행을 읽을 것으로 예상한다.
- Using filesort: 정렬을 위해 따로 정렬 작업을 한다.
실행 계획 항목 설명은 EXPLAIN 보는 방법에 정리했다.
느린 쿼리 개선 2단계 인덱스 설계
WHERE에서 같다(=)로 거르는 컬럼을 앞에, ORDER BY 컬럼을 뒤에 두는 복합 인덱스를 만든다. 이렇게 하면 고객으로 범위를 좁힌 뒤 이미 날짜순으로 정렬된 인덱스를 그대로 읽을 수 있다.
ALTER TABLE orders ADD INDEX idx_customer_ordered (customer, ordered_at);
MySQL 8.0의 InnoDB는 보조 인덱스 추가를 대부분 온라인으로 처리해 읽기와 쓰기를 막지 않는다. 그래도 큰 테이블은 시간이 걸리고 서버 부하가 생기니 사용자가 적은 시간에 하고, 작업 전에는 백업을 해 두자. 복합 인덱스 컬럼 순서는 MySQL 인덱스 설계 원칙에서 자세히 다뤘다.
느린 쿼리 개선 3단계 전후 비교
EXPLAIN SELECT id, amount, ordered_at FROM orders
WHERE customer = 'kim' ORDER BY ordered_at DESC LIMIT 20;
-- type: ref, key: idx_customer_ordered, rows: 1240, Extra: Backward index scan
| 항목 | 전 | 후 |
|---|---|---|
| type | ALL | ref |
| 읽는 행(예상) | 약 50만 | 약 1,240 |
| 정렬 | filesort | 인덱스 역순 읽기 |
| 실행 시간 | 1.8초 | 0.003초 |
표의 숫자는 예시지만, 이런 형태의 쿼리에서 이 정도 차이는 흔하다. 개선 뒤에는 느린 쿼리 로그에서 같은 쿼리가 더 나오지 않는지 하루 정도 지켜본다.
인덱스를 늘릴 때 주의
- 인덱스는 쓰기(INSERT, UPDATE)를 느리게 하고 디스크를 쓴다. 꼭 필요한 것만 만든다.
- 이미 있는 인덱스와 앞쪽 컬럼이 겹치면 하나로 합칠 수 있는지 본다.
- LIKE ‘%검색어%’처럼 앞에 %가 붙은 검색은 일반 인덱스로 해결되지 않는다.
인덱스 최적화 원리는 MySQL 공식 최적화 문서에 있다.
느린 쿼리 개선 정리
측정(느린 쿼리 로그), 진단(EXPLAIN), 처방(인덱스), 확인(전후 비교)의 네 단계를 지키면 감에 의존하지 않고 쿼리를 고칠 수 있다. 전날 글은 포트 연결 테스트다.
첫 댓글을 남겨보세요