옵티마이저가 인덱스를 무시하는 이유 — 통계와 ANALYZE
"인덱스를 만들었는데 안 타요"의 진짜 원인
이 질문을 받으면 저는 거의 항상 같은 순서로 확인합니다. 함수를 컬럼에 씌웠는지, 타입이 안 맞는지, 그리고 마지막으로 통계입니다. 앞의 두 가지는 원인을 알면 바로 고칠 수 있으니 먼저 정리하겠습니다.
-- 인덱스: created_at
-- 안 탄다: 컬럼에 함수를 씌워서 원래 값이 아니게 됐다
SELECT * FROM orders WHERE date_trunc('day', created_at) = '2026-03-14';
-- 탄다: 함수 자체를 인덱스로 만들면 된다 (표현식 인덱스)
CREATE INDEX idx_orders_created_day ON orders (date_trunc('day', created_at));-- 인덱스: customer_id (integer)
-- 안 탈 수 있다: 파라미터 타입이 안 맞으면 암묵적 캐스팅이 끼어든다
SELECT * FROM orders WHERE customer_id = '42'; -- 텍스트 리터럴이 두 가지를 다 확인했는데도 여전히 Seq Scan 이 나온다면, 이제 통계 차례입니다.
옵티마이저는 실제 데이터를 안 본다
옵티마이저는 쿼리를 계획할 때 테이블을 직접 들여다보지 않습니다. ANALYZE 가 만들어둔 통계 스냅샷(pg_stats)을 보고 "이 조건이면 대략 몇 행이 나올까"를 추정할 뿐입니다. 이 추정이 틀리면, 옵티마이저 입장에서는 합리적인 계획인데 실제로는 최악의 계획을 고르는 상황이 벌어집니다.
SELECT attname, n_distinct, most_common_vals, correlation
FROM pg_stats
WHERE tablename = 'orders' AND attname = 'status';n_distinct 는 고유값 개수 추정치입니다. 이 값이 실제와 크게 다르면(가장 흔한 원인은 ANALYZE 가 오래전에 돌고 그 뒤로 데이터 분포가 확 바뀐 경우) 옵티마이저는 "이 조건이면 3행 나올 것"이라 믿고 Nested Loop 를 골랐는데 실제로는 30만 행이 나와서 쿼리가 몇 분씩 걸리는 사고가 생깁니다.
ANALYZE orders;
EXPLAIN ANALYZE SELECT * FROM orders WHERE status = 'refunded';
-- rows=3 (estimated) vs rows=180000 (actual) 처럼 추정과 실측이
-- 크게 벌어지면 이게 바로 그 신호입니다표본 크기를 늘려서 정밀도를 높인다
기본적으로 ANALYZE 는 테이블 전체를 보지 않고 일부만 표본으로 뽑습니다(default_statistics_target, 기본값 100). 값의 분포가 고르지 않은 컬럼(소수의 값이 압도적으로 많이 나오는 경우)에서는 이 표본 크기로 부족할 수 있습니다.
ALTER TABLE orders ALTER COLUMN status SET STATISTICS 500;
ANALYZE orders;전역 설정을 올리면 모든 테이블·컬럼의 ANALYZE 비용이 늘어나므로, 저는 문제가 확인된 컬럼에만 개별적으로 STATISTICS 를 올리는 편을 선호합니다.
컬럼 두 개가 서로 연관되어 있으면 통계가 또 틀린다
city = 'Seoul' AND country = 'South Korea' 같은 조건은 옵티마이저 입장에서 두 조건이 독립이라고 가정하고 각각의 선택률을 곱합니다. 하지만 실제로는 city 가 'Seoul' 이면 country 는 거의 항상 'South Korea' 입니다. 독립을 가정하면 실제보다 훨씬 적은 행이 나올 거라고 과소 추정하게 됩니다. 이럴 때는 확장 통계(extended statistics)로 두 컬럼의 상관관계를 명시적으로 알려줄 수 있습니다.
CREATE STATISTICS stx_orders_city_country (dependencies)
ON city, country FROM customers;
ANALYZE customers;체감상 이 기능까지 손대야 하는 경우는 흔치 않습니다. 대부분의 문제는 ANALYZE 를 안 돌렸거나 표본이 부족해서 생깁니다. 하지만 EXPLAIN ANALYZE 로 추정과 실측이 계속 크게 벌어지는데 앞의 방법으로도 안 잡힌다면, 컬럼 간 상관관계를 의심해볼 차례입니다.