설치·접속: PostgreSQL 설치와 접속
부제: (user_id, status) 복합 인덱스를 걸었는데, WHERE status=... 만 쓰는 쿼리는 여전히 Seq Scan일 때
CREATE INDEX idx_m5_us ON m5_orders(user_id, status);
-- Q1 선두 컬럼만: 인덱스 O
EXPLAIN (ANALYZE, COSTS OFF) SELECT * FROM m5_orders WHERE user_id = 12345;
-- Q2 선두+두번째: 인덱스 O (둘 다 조건으로)
EXPLAIN (ANALYZE, COSTS OFF) SELECT * FROM m5_orders WHERE user_id = 12345 AND status = 'done';
-- Q3 두번째 컬럼만: 인덱스 X → Seq Scan
EXPLAIN (ANALYZE, COSTS OFF) SELECT * FROM m5_orders WHERE status = 'pending';
-- Q1 user_id 단독: Bitmap Index Scan, Index Cond: (user_id = 12345) 0.167 ms
-- Q2 둘 다: Index Cond: ((user_id = 12345) AND (status='done')) 0.024 ms
-- Q3 status 단독:
Seq Scan on m5_orders (actual time=0.014..80.363 rows=200000 loops=1)
Filter: (status = 'pending'::text)
Rows Removed by Filter: 1800000
Execution Time: 85.518 ms복합 인덱스는 사전(dictionary) 정렬과 같다. (user_id, status)는 먼저 user_id로 줄을 세우고, 그 안에서 status로 다시 세운다. 그래서 선두 컬럼(user_id)이 조건에 있어야 사전을 펼칠 지점을 찾는다. Q1은 user_id만으로도 인덱스를 타고(선두 O), Q2는 user_id로 좁힌 뒤 그 안에서 status까지 좁힌다(둘 다 Index Cond). 반면 Q3는 선두 user_id 조건이 없다 — 성(姓)을 모른 채 이름만으로 전화번호부를 찾는 격이라 인덱스를 펼칠 수 없어 Seq Scan으로 200만 행을 통째로 훑는다(85 ms). 규칙: 자주 단독으로 검색되는 컬럼을 선두에 둔다. 등호 조건 컬럼을 앞에, 범위 조건 컬럼을 뒤에 두는 것도 같은 이유다(범위가 앞에 오면 그 뒤 컬럼의 정렬이 깨져 못 좁힌다).
이렇게도 쓴다
status를 단독으로도 검색해야 한다면 순서를 뒤집은 인덱스를 따로 둔다. (조합: 반대 순서)
CREATE INDEX idx_m5_su ON m5_orders(status, user_id);
등호는 앞, 범위는 뒤 — created_at 범위 조회가 잦다면 이 순서로. (원칙: equality→range)
CREATE INDEX idx_m5_uc ON m5_orders(user_id, created_at);
-- WHERE user_id=1 AND created_at >= '2024-06-01' 에 최적
선두 컬럼만 쓸 거면 두 번째 컬럼은 INCLUDE로 얹어 인덱스만 읽게 한다. (조합: covering)
CREATE INDEX idx_m5_u_inc ON m5_orders(user_id) INCLUDE (amount);
정말 인덱스를 탔는지, 어느 조건이 Index Cond로 갔는지 확인한다. (조합: EXPLAIN)
EXPLAIN (COSTS OFF) SELECT * FROM m5_orders WHERE user_id=1 AND status='done';
컬럼별 선택도(고유값 수)를 보고 어떤 컬럼을 선두로 둘지 정한다. (조합: pg_stats)
SELECT attname, n_distinct FROM pg_stats WHERE tablename='m5_orders';
중복이 된 (user_id) 단일 인덱스는 복합 인덱스가 대신하므로 지운다. (조합: 중복 정리)
DROP INDEX idx_orders_user; -- (user_id,status)가 선두로 커버'명령어 > DB' 카테고리의 다른 글
| [PostgreSQL] 데드락 두 트랜잭션이 서로의 잠금을 기다리다 하나가 강제 중단될 때 (0) | 2026.07.23 |
|---|---|
| [PostgreSQL] 확장 통계(CREATE STATISTICS)로 상관 컬럼 추정 오차 잡기 (0) | 2026.07.23 |
| [PostgreSQL] BRIN 인덱스로 200만 행 시계열을 24kB로 색인한다 (0) | 2026.07.23 |
| [PostgreSQL] pg_advisory_lock 크론·배치가 두 번 동시에 도는 걸 앱 레벨 뮤텍스로 막는다 (0) | 2026.07.23 |
| [PostgreSQL] FETCH FIRST n ROWS WITH TIES — 동점까지 포함한 Top-N (0) | 2026.07.23 |