/엔지니어/데이터베이스/슬로우 쿼리 분석 — pg_stat_statemen
데이터베이스중급UbuntuDebianCentOS슬로우쿼리pg_stat_statementsexplain

슬로우 쿼리 분석 — pg_stat_statements · MySQL slow log

PostgreSQL pg_stat_statements로 느린 쿼리를 찾고, MySQL slow query log와 EXPLAIN ANALYZE로 인덱스 문제를 진단해 쿼리를 최적화하는 방법을 설명합니다.

PostgreSQL — pg_stat_statements

SQL
-- 확장 활성화 (postgresql.conf)
-- shared_preload_libraries = 'pg_stat_statements'

-- DB에서 확장 설치
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;

-- 설정 확인
SHOW shared_preload_libraries;
Bash
# postgresql.conf 수정
sudo nano /etc/postgresql/16/main/postgresql.conf

# 추가:
# shared_preload_libraries = 'pg_stat_statements'
# pg_stat_statements.track = all
# pg_stat_statements.max = 10000

sudo systemctl restart postgresql

느린 쿼리 Top 10 찾기 (PostgreSQL)

SQL
-- 평균 실행 시간 기준 Top 10
SELECT
  round(mean_exec_time::numeric, 2) AS avg_ms,
  calls,
  round(total_exec_time::numeric, 2) AS total_ms,
  rows,
  left(query, 80) AS query
FROM pg_stat_statements
ORDER BY mean_exec_time DESC
LIMIT 10;

-- 총 실행 시간 기준 (가장 영향 큰 쿼리)
SELECT
  round(total_exec_time::numeric / 1000, 2) AS total_sec,
  calls,
  round(mean_exec_time::numeric, 2) AS avg_ms,
  left(query, 80) AS query
FROM pg_stat_statements
ORDER BY total_exec_time DESC
LIMIT 10;

-- I/O 많은 쿼리
SELECT
  left(query, 80),
  shared_blks_read,
  shared_blks_hit,
  round(shared_blks_hit * 100.0 / nullif(shared_blks_hit + shared_blks_read, 0), 1) AS hit_rate
FROM pg_stat_statements
ORDER BY shared_blks_read DESC
LIMIT 10;

-- 통계 초기화
SELECT pg_stat_statements_reset();

EXPLAIN ANALYZE — 실행 계획 분석

SQL
-- 기본 실행 계획
EXPLAIN SELECT * FROM users WHERE email = '[email protected]';

-- 실제 실행 + 통계
EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT)
SELECT u.name, COUNT(o.id)
FROM users u
JOIN orders o ON u.id = o.user_id
WHERE u.status = 'active'
GROUP BY u.name
ORDER BY COUNT(o.id) DESC;

실행 계획 주요 포인트

CODE
Seq Scan     → 순차 스캔 (느림, 인덱스 필요)
Index Scan   → 인덱스 사용 (빠름)
Bitmap Scan  → 중간 정도
Hash Join    → 대용량 조인
Nested Loop  → 소용량 조인

Actual Rows >> Estimated Rows → 통계 오래됨
cost=0..1234 → 실행 비용 추정
actual time=0.1..500 ms → 실제 시간
SQL
-- 통계 갱신
ANALYZE users;
ANALYZE VERBOSE users;

MySQL — Slow Query Log

INI
# /etc/mysql/mysql.conf.d/mysqld.cnf
slow_query_log       = 1
slow_query_log_file  = /var/log/mysql/slow.log
long_query_time      = 1          # 1초 이상
log_queries_not_using_indexes = 1 # 인덱스 안 쓰는 쿼리도 기록
Bash
sudo systemctl restart mysql

# 실시간 모니터링
sudo tail -f /var/log/mysql/slow.log

# mysqldumpslow로 분석
sudo mysqldumpslow -s t -t 10 /var/log/mysql/slow.log   # 총 시간 기준 Top 10
sudo mysqldumpslow -s c -t 10 /var/log/mysql/slow.log   # 호출 횟수 기준

MySQL EXPLAIN

SQL
EXPLAIN SELECT u.name, COUNT(o.id)
FROM users u
JOIN orders o ON u.id = o.user_id
WHERE u.status = 'active'
GROUP BY u.name;

-- 주요 컬럼
-- type: ALL(풀스캔) < index < range < ref < eq_ref < const (좋아짐)
-- key: 사용된 인덱스
-- rows: 검사 예상 행 수
-- Extra: Using filesort, Using temporary → 성능 문제

공통 최적화 패턴

SQL
-- 1. 인덱스 없는 WHERE 조건 → 인덱스 추가
CREATE INDEX idx_users_status_email ON users(status, email);

-- 2. SELECT * → 필요한 컬럼만
SELECT id, name FROM users WHERE status = 'active';

-- 3. N+1 문제 → JOIN으로 해결
-- 나쁜 예: 루프에서 각 유저마다 쿼리
-- 좋은 예:
SELECT u.id, u.name, o.id, o.total
FROM users u
JOIN orders o ON u.id = o.user_id
WHERE u.status = 'active';

-- 4. OFFSET 대신 커서 페이징
-- 나쁜 예: LIMIT 10 OFFSET 10000
-- 좋은 예:
SELECT * FROM users WHERE id > 10000 ORDER BY id LIMIT 10;
#슬로우쿼리#pg_stat_statements#explain#mysql#postgresql#성능
편집 안내 · Editorial Note

이 가이드는 AI 도구를 활용해 초안을 구성하고 사람이 명령어·문맥을 검토해 발행했습니다. 운영체제와 도구 버전에 따라 결과가 달라질 수 있으므로 적용 전 공식 문서를 함께 확인하세요. 오류를 발견하시면 이메일로 제보해 주세요.

질문 & 답변 (Q&A)

이 가이드에 대해 궁금한 점을 질문해보세요. 확인 후 답변드립니다.