옵티마이저가 "이 조건이면 몇 행이 나올까" 를 추정하는 근거 데이터. 튜닝의 토대다.
무엇이 들어 있나
테이블 행 수 칼럼별 서로 다른 값의 개수(카디널리티) 히스토그램 — 값이 어느 구간에 몰려 있나 NULL 비율 칼럼별 평균 길이
히스토그램이 중요한 이유는 분포가 균등하지 않기 때문이다. status 칼럼에 값이 3종류 있어도 PAID가 99%이고 PENDING이 1%라면, 두 조건의 최적 계획이 다르다. 히스토그램이 없으면 옵티마이저는 균등 분포를 가정해 둘 다 틀린다.
낡으면 무슨 일이 생기나
대량 INSERT 직후 — 통계는 옛 데이터를 반영하고 있다
실제 100만 행 ←→ 통계상 100행
↓
옵티마이저: "100행이면 인덱스로 100번 타는 게 싸겠다"
↓
실제로는 100만 번 인덱스를 탄다 → 풀 스캔보다 훨씬 느리다
추정이 틀리면 계획이 망한다. 그리고 이건 서서히 나빠지는 게 아니라 어느 순간 갑자기 나타난다 — "어제까진 빨랐는데 오늘 갑자기 느려요"의 흔한 원인이다.
갱신하는 법
-- PostgreSQL: autovacuum 의 auto-analyze 가 자동 갱신. 수동은
ANALYZE orders;
ANALYZE; -- 전체 DB
-- MySQL / InnoDB (통계를 샘플링한다)
ANALYZE TABLE orders;
대량 적재·대량 삭제·마이그레이션 직후에는 수동으로 갱신하는 것이 정석이다. 자동 갱신은 변경 비율이 임계치를 넘어야 도는데, 배치 작업은 그 사이에 이미 쿼리를 돌리기 시작하기 때문이다.
통계가 문제인지 확인하는 법
EXPLAIN ANALYZE SELECT ...;
-- (estimated rows=100 actual rows=1000000) ← 1만 배 차이 = 통계가 거짓말
estimated와 actual의 자릿수가 다르면 통계부터 의심한다. 인덱스를 추가하기 전에 ANALYZE를 먼저 돌려 보는 것이 순서다.
면접 함정
- ❌ "통계는 DB가 알아서 관리한다" → 자동 갱신은 임계치 기반이라 대량 변경 직후에는 늦다.
- ❌ "쿼리가 느려지면 인덱스를 추가한다" → 통계 확인이 먼저다. 인덱스는 이미 있는데 옵티마이저가 안 쓰는 경우가 흔하다.
자동 갱신 임계치를 조정하기
-- PostgreSQL 기본: 변경 행이 (10% + 50) 를 넘으면 auto-analyze
ALTER TABLE orders SET (autovacuum_analyze_scale_factor = 0.02); -- 2% 로 낮춘다
-- 마지막으로 언제 갱신됐나
SELECT relname, last_analyze, last_autoanalyze, n_mod_since_analyze
FROM pg_stat_user_tables ORDER BY n_mod_since_analyze DESC LIMIT 10;
n_mod_since_analyze가 크면 통계가 실제 데이터를 못 따라가고 있다는 뜻이다.
배치 작업의 정석
BEGIN;
-- 대량 적재
COPY orders FROM '/tmp/orders.csv' CSV;
COMMIT;
ANALYZE orders; -- ← 이 한 줄을 빼먹어서 다음 날 장애가 난다
-- MySQL
LOAD DATA INFILE '/tmp/orders.csv' INTO TABLE orders;
ANALYZE TABLE orders;
칼럼 간 상관관계는 기본 통계로 못 잡는다
-- '서울' 이면서 '강남구' 인 조건 — 두 칼럼은 독립이 아니다
-- 기본 통계는 독립을 가정해 행 수를 크게 과소추정한다
CREATE STATISTICS stat_addr (dependencies) ON city, district FROM address;
ANALYZE address;
다변량 통계는 PostgreSQL 10+의 기능으로, 조건이 여러 칼럼에 걸릴 때 오추정을 크게 줄인다.