들어가며: 슬로우 쿼리를 “나중에 보겠다”고 미루면 생기는 일
MySQL 운영에서 성능 문제는 대부분 소수의 느린 쿼리가 원인이다. 그런데 Slow Query Log를 켜지 않았거나, 켜놓고도 분석하지 않는 팀이 많다. 장애가 터진 뒤 SHOW PROCESSLIST로 급하게 범인을 찾는 것은 이미 늦은 대응이다.
이 글에서는 MySQL 8.0/8.4 공식 문서(The Slow Query Log, Server System Variables)를 근거로, Slow Query Log의 설정·분석·자동화까지 운영 관점에서 정리한다.
1. Slow Query Log 활성화: 핵심 변수 4가지
1-1. 필수 설정
| 변수 | 기본값 | 권장값 | 설명 |
|---|---|---|---|
slow_query_log |
OFF | ON | 슬로우 쿼리 로그 활성화 |
long_query_time |
10 (초) | 1 (또는 0.5) | 이 값을 초과하는 쿼리가 기록됨. 10초는 너무 느슨하다 |
log_queries_not_using_indexes |
OFF | ON | 인덱스를 사용하지 않는 쿼리도 기록 |
slow_query_log_file |
호스트명-slow.log | 경로 명시 | 로그 파일 경로 |
1-2. 런타임 즉시 적용 (재시작 불필요)
-- 런타임에서 바로 켜기
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1;
SET GLOBAL log_queries_not_using_indexes = 'ON';
-- 확인
SHOW VARIABLES LIKE 'slow_query%';
SHOW VARIABLES LIKE 'long_query_time';
MySQL 공식 문서에 따르면, 이 변수들은 GLOBAL과 SESSION 모두 동적으로 변경 가능하다. 단, 런타임 변경은 MySQL 재시작 시 초기화되므로 my.cnf에도 반영해야 한다.
1-3. my.cnf 영구 설정
[mysqld]
slow_query_log = 1
long_query_time = 1
log_queries_not_using_indexes = 1
slow_query_log_file = /var/log/mysql/slow.log
log_throttle_queries_not_using_indexes = 60
log_throttle_queries_not_using_indexes는 분당 기록할 인덱스 미사용 쿼리 수를 제한한다. 인덱스 없는 쿼리가 대량 발생할 때 로그 폭발을 방지한다(MySQL 공식 문서 Server System Variables).
2. 슬로우 쿼리 로그 포맷 읽기
2-1. 로그 엔트리 구조
# Time: 2026-02-19T08:15:32.456789Z
# User@Host: app_user[app_user] @ 10.0.0.100 [10.0.0.100] Id: 12345
# Query_time: 3.456789 Lock_time: 0.000123 Rows_sent: 1 Rows_examined: 1500000
SET timestamp=1771488932;
SELECT * FROM orders WHERE created_at > '2026-01-01' AND status = 'PENDING';
2-2. 핵심 필드 해석
| 필드 | 의미 | 주의점 |
|---|---|---|
Query_time |
쿼리 실행 시간 (초) | Lock 대기 시간 포함 |
Lock_time |
테이블/행 락 대기 시간 | 높으면 동시성 문제 의심 |
Rows_sent |
클라이언트에 전송된 행 수 | — |
Rows_examined |
서버가 검사한 행 수 | Rows_examined ≫ Rows_sent이면 인덱스 개선 필요 |
핵심 지표: Rows_examined / Rows_sent 비율이 높을수록 비효율적인 쿼리다. 이상적으로는 1:1에 가까워야 한다.
3. mysqldumpslow: 빌트인 분석 도구
MySQL에 기본 포함된 mysqldumpslow는 슬로우 쿼리 로그를 요약 분석한다.
# 가장 느린 쿼리 Top 10
mysqldumpslow -s t -t 10 /var/log/mysql/slow.log
# 가장 많이 실행된 느린 쿼리 Top 10
mysqldumpslow -s c -t 10 /var/log/mysql/slow.log
# 정렬 옵션
# -s t : 총 실행 시간 순
# -s c : 실행 횟수 순
# -s l : 락 대기 시간 순
# -s r : 반환 행 수 순
# -s at : 평균 실행 시간 순
3-1. 출력 예시 해석
Count: 1523 Time=2.34s (3564s) Lock=0.00s (0s) Rows=1.0 (1523)
SELECT * FROM orders WHERE status = 'S' AND created_at > 'S'
| 항목 | 의미 |
|---|---|
Count: 1523 |
이 패턴의 쿼리가 1,523번 실행됨 |
Time=2.34s (3564s) |
평균 2.34초, 총 3,564초 소모 |
Rows=1.0 (1523) |
평균 1행 반환, 총 1,523행 |
'S' |
문자열 리터럴은 'S'로, 숫자는 N으로 추상화됨 |
mysqldumpslow는 리터럴 값을 추상화하여 같은 패턴의 쿼리를 그룹화한다. 이를 통해 “어떤 쿼리 패턴이 전체 부하의 몇 %를 차지하는가”를 파악할 수 있다.
4. pt-query-digest: 고급 분석 (Percona Toolkit)
mysqldumpslow보다 상세한 분석이 필요하면 Percona Toolkit의 pt-query-digest를 사용한다.
# 설치 (Debian/Ubuntu)
apt-get install percona-toolkit
# 분석
pt-query-digest /var/log/mysql/slow.log
# 특정 시간 범위만 분석
pt-query-digest --since '2026-02-19 00:00:00' --until '2026-02-19 12:00:00' \
/var/log/mysql/slow.log
4-1. pt-query-digest 출력 구조
출력은 세 부분으로 구성된다:
- Overall summary: 전체 쿼리 수, 총 실행 시간, 시간당 QPS
- Profile: 쿼리 패턴별 순위 (총 실행 시간 비율)
- Query detail: 각 패턴의 실행 시간 분포(min/max/median/95th percentile), EXPLAIN 결과
# Profile 예시
# Rank Query ID Response time Calls R/Call
# ==== ============================== =============== ====== ======
# 1 0xABCD1234567890AB 3564.00 45.2% 1523 2.34
# 2 0xEFGH5678901234CD 1890.00 24.0% 890 2.12
# 3 0xIJKL9012345678EF 980.00 12.4% 4500 0.22
Response time 비율이 높은 순서대로 최적화하는 것이 가장 효과적이다. 1위 쿼리만 최적화해도 전체 부하의 45%를 줄일 수 있다.
5. 슬로우 쿼리 → EXPLAIN → 인덱스 설계 워크플로
5-1. 단계별 흐름
- 슬로우 쿼리 로그에서 상위 쿼리 추출:
mysqldumpslow -s t -t 5 - EXPLAIN으로 실행 계획 확인:
type,Extra,rows확인 - 인덱스 설계/수정: WHERE → ORDER BY → SELECT 순서로 복합 인덱스 컬럼 배치
- EXPLAIN ANALYZE로 실측 검증: 변경 전후 actual time 비교
- 배포 후 슬로우 쿼리 로그 재확인: 해당 패턴이 사라졌는지 확인
5-2. 예시: 슬로우 쿼리 최적화
-- 슬로우 쿼리 로그에서 발견된 쿼리
-- Query_time: 3.45 Rows_examined: 1500000 Rows_sent: 23
SELECT order_id, total_amount, created_at
FROM orders
WHERE status = 'PENDING'
AND created_at > '2026-01-01'
ORDER BY created_at DESC
LIMIT 20;
-- EXPLAIN 결과: type=ALL, rows=1500000, Extra=Using where; Using filesort
-- 인덱스 추가
ALTER TABLE orders ADD INDEX idx_status_created (status, created_at);
-- EXPLAIN 결과: type=range, rows=23, Extra=Using index condition
-- Query_time: 0.002초로 개선
6. 운영 자동화: 로그 로테이션과 모니터링
6-1. 로그 로테이션
슬로우 쿼리 로그가 무한히 커지는 것을 방지해야 한다.
# /etc/logrotate.d/mysql-slow
/var/log/mysql/slow.log {
daily
rotate 14
compress
delaycompress
missingok
notifempty
create 640 mysql mysql
postrotate
/usr/bin/mysqladmin flush-logs
endscript
}
flush-logs는 MySQL이 로그 파일을 닫고 새 파일을 열게 한다. 이것이 없으면 MySQL은 삭제된 파일에 계속 쓴다.
6-2. Performance Schema 연계
MySQL 8.0+에서는 Performance Schema의 events_statements_summary_by_digest 테이블로 슬로우 쿼리 로그 없이도 쿼리 통계를 조회할 수 있다:
SELECT
DIGEST_TEXT,
COUNT_STAR AS exec_count,
ROUND(SUM_TIMER_WAIT / 1e12, 2) AS total_sec,
ROUND(AVG_TIMER_WAIT / 1e12, 4) AS avg_sec,
SUM_ROWS_EXAMINED,
SUM_ROWS_SENT
FROM performance_schema.events_statements_summary_by_digest
ORDER BY SUM_TIMER_WAIT DESC
LIMIT 10;
Performance Schema는 메모리 기반이므로 디스크 I/O 부담이 없다. 슬로우 쿼리 로그와 함께 사용하면 로그 파일(상세 분석)과 실시간 집계(대시보드)를 모두 확보할 수 있다.
6-3. sys 스키마 활용
-- 가장 비용이 큰 쿼리 Top 10 (sys 스키마)
SELECT * FROM sys.statements_with_runtimes_in_95th_percentile
LIMIT 10;
-- 인덱스를 사용하지 않는 쿼리
SELECT * FROM sys.statements_with_full_table_scans
ORDER BY no_index_used_count DESC
LIMIT 10;
-- 사용되지 않는 인덱스
SELECT * FROM sys.schema_unused_indexes;
7. 실전 체크리스트
- Slow Query Log 활성화:
slow_query_log = ON,long_query_time = 1(또는 팀 SLA 기준)으로 설정했는가? - 인덱스 미사용 쿼리 기록:
log_queries_not_using_indexes = ON을 켰는가?log_throttle_queries_not_using_indexes로 로그 폭발을 방지했는가? - 정기 분석 루틴: 주 1회 이상
mysqldumpslow또는pt-query-digest로 상위 쿼리를 분석하는가? - Rows_examined/Rows_sent 비율: 비율이 100:1 이상인 쿼리를 인덱스 최적화 대상으로 분류하는가?
- 로그 로테이션: logrotate +
flush-logs로 로그 파일 크기를 관리하는가? - Performance Schema 대시보드:
events_statements_summary_by_digest를 Grafana 등에 연결하여 실시간 모니터링하는가?
8. 흔한 실수와 방지법
| 실수 | 증상 | 방지법 |
|---|---|---|
long_query_time = 10 기본값 유지 |
1~9초 걸리는 문제 쿼리가 기록되지 않음 | 1초 이하로 설정. 시작은 1초, 점진적으로 낮춤 |
| 로그 로테이션 미설정 | 슬로우 쿼리 로그가 수십 GB로 증가, 디스크 풀 | logrotate 설정 + flush-logs postrotate |
| 로그만 쌓고 분석하지 않음 | 장애 발생 후 급하게 뒤지는 사후 대응 | 주간 pt-query-digest 크론 잡 + 팀 리뷰 |
| EXPLAIN 없이 인덱스 추가 | 불필요한 인덱스 생성 → 쓰기 성능 저하 | 반드시 EXPLAIN으로 현재 실행 계획 확인 후 인덱스 설계 |
정리
MySQL Slow Query Log는 성능 문제의 사전 탐지와 지속적 개선을 위한 가장 기본적인 도구다. 핵심 워크플로는 단순하다: 로그 켜기 → 정기 분석(mysqldumpslow/pt-query-digest) → EXPLAIN → 인덱스 설계 → EXPLAIN ANALYZE 검증. 여기에 Performance Schema/sys 스키마 실시간 모니터링을 더하면, 대부분의 MySQL 성능 문제를 장애 전에 발견하고 해결할 수 있다.