같은 쿼리인데 EXPLAIN은 Index Scan을 보여주고, EXPLAIN ANALYZE로 실제 돌리면 Seq Scan이 찍힙니다.
EXPLAIN
SELECT * FROM orders WHERE user_id = 123 AND created_at >= '2026-01-01';
-- 결과: Index Scan using idx_orders_user_id
EXPLAIN ANALYZE
SELECT * FROM orders WHERE user_id = 123 AND created_at >= '2026-01-01';
-- 결과: Seq Scan on orders (cost=... actual time=...)
이게 어떻게 된 일인가요? 플래너가 거짓말을 하는 건가요?
댓글 3
거짓말은 아닙니다.
EXPLAIN(ANALYZE 없음)은 실행을 하지 않고 계획만 보여줍니다. 그리고 PostgreSQL 플래너는 실행 직전에 통계 기반으로 계획을 다시 결정합니다. 사이에 다음 중 하나가 발생하면 계획이 바뀝니다.ANALYZE이후 데이터가 많이 바뀌어 실행 시점엔 Seq가 싸다고 판단1차 진단
123인 행이 5% 이상이면 플래너가 Seq를 고르는 게 정상입니다.
2차: random_page_cost
기본값 4.0은 HDD 기준입니다. SSD/NVMe면 1.1~1.5로 내리는 게 표준.
3차: 복합 인덱스로 유도
조건이
user_id와created_at복합이면 복합 인덱스가 답입니다.4차: prepared statement 함정
드라이버에서
prepare후execute를 반복하면 generic plan이 캐시됩니다. 특정 파라미터에서만 이상하다면 이게 원인입니다.plan_cache_mode = force_custom_plan으로 세션에서 강제 가능.보통은 1번(통계 드리프트)과 2번(cost 파라미터)로 80% 이상 해결됩니다.
저는 prod에서
auto_explain을 기본으로 켜놔서, 느린 쿼리의 실제 plan이 로그에 남도록 해둡니다. EXPLAIN만으론 진실이 안 보여서요.이거 도입하면 plan 문제의 90%는 자연스럽게 추적됩니다.
ANALYZE 한번 돌리니까 실제로 Index Scan으로 바뀌네요. 통계가 하루 전이었어요. random_page_cost도 1.1로 내렸습니다. auto_explain은 운영에 바로 넣어야겠네요. 감사합니다!