본문으로 건너뛰기
김신건의 로그

[DB] EXPLAIN 읽기: scan, join, sort, cost

· 수정 · 📖 약 2분 · 594자/단어 #explain #query-plan #postgresql #performance #database
EXPLAIN, EXPLAIN ANALYZE, query plan, Seq Scan, Index Scan, Hash Join, Nested Loop, Merge Join

정의

EXPLAIN = 쿼리 실행 계획 표시. EXPLAIN ANALYZE = 실제로 실행 + 실제 시간 측정.

EXPLAIN (ANALYZE, BUFFERS, VERBOSE, FORMAT TEXT)
SELECT u.name, COUNT(o.id)
FROM users u LEFT JOIN orders o ON o.user_id = u.id
WHERE u.created_at > '2026-01-01'
GROUP BY u.id, u.name;

Scan 종류

flowchart TD
    Scan{Scan 종류}
    Scan --> Seq["Seq Scan<br/>전체 테이블 순회"]
    Scan --> Idx["Index Scan<br/>인덱스 → 테이블"]
    Scan --> IdxOnly["Index Only Scan<br/>인덱스에서만"]
    Scan --> Bitmap["Bitmap Scan<br/>bitmap 으로 페이지 모음"]
Scan의미적합
Seq Scan전체 테이블작은 테이블, 대부분 row 반환
Index Scan인덱스 → row선택적 (적은 row)
Index Only Scan인덱스 only (heap 안 봄)covering index
Bitmap Index Scan여러 인덱스 결합 + bitmap다 인덱스 조합
Bitmap Heap Scanbitmap → 페이지 fetch위와 짝
Tid Scan직접 tid 접근극히 드뭄

Join 종류

Join동작적합
Nested Loopfor outer { for inner }작은 inner + 인덱스
Hash Joinhash 빌드 → 룩업큰 양쪽, 등가 조건
Merge Join정렬된 양쪽 머지양쪽 정렬됨, 범위 조건

위 애니메이션은 Grace Hash Join (메모리 부족 시 분할 hash). 대용량 join 의 일반 직관.

EXPLAIN ANALYZE 출력 읽기

Aggregate  (cost=1024.50..1024.51 rows=1 width=8) (actual time=15.234..15.236 rows=1 loops=1)
  ->  Hash Join  (cost=12.50..1020.50 rows=1600 width=4) (actual time=0.234..14.123 rows=1542 loops=1)
        Hash Cond: (o.user_id = u.id)
        ->  Seq Scan on orders o  (cost=0.00..900.00 rows=50000 width=8) (actual time=0.012..5.234 rows=49832 loops=1)
        ->  Hash  (cost=10.00..10.00 rows=200 width=8) (actual time=0.190..0.190 rows=215 loops=1)
              ->  Index Scan using idx_users_created on users u  (cost=0.42..10.00 rows=200 width=8) (actual time=0.012..0.123 rows=215 loops=1)
                    Index Cond: (created_at > '2026-01-01'::date)
Planning Time: 0.345 ms
Execution Time: 15.456 ms
필드의미
cost=A..BA = 시작 비용, B = 총 비용 (페이지 수 단위)
rows=N예상 row 수
width=N평균 row 바이트
actual time=A..B실제 시작/종료 ms
rows=actual실제 row 수
loops=N이 노드가 N번 실행
Planning Timeoptimizer 시간
Execution Time실행 시간

IMPORTANT

예상 vs 실제 row 수 차이가 수십배통계 부정확ANALYZE table_name 으로 갱신.

자주 보는 안티 패턴

1. Seq Scan on 큰 테이블

Seq Scan on big_table  (rows=10000000)
  Filter: (status = 'pending')
  Rows Removed by Filter: 9990000

→ 인덱스 추가 또는 partial index (WHERE status = 'pending').

2. Nested Loop on 큰 양쪽

Nested Loop  (loops=100000)
  ->  Index Scan on table_a
  ->  Index Scan on table_b

→ 한쪽이 수만 rowHash Join 으로 가야 함. set enable_nestloop = off 로 테스트.

3. Sort + Disk

Sort  (Sort Method: external merge  Disk: 1234kB)

work_mem 부족. session 또는 query 단위 증가.

외부 정렬 (disk spill) 동작 직관. 임시 파일이 디스크에 쓰임.

4. Bitmap Heap Scan + 작은 rows

Bitmap Heap Scan  (rows=10)
  ->  Bitmap Index Scan

→ 좋은 패턴. 인덱스가 적절히 작동.

자주 쓰는 옵션

EXPLAIN (
  ANALYZE,      -- 실제 실행
  BUFFERS,      -- shared/local/temp buffer hits
  VERBOSE,      -- 컬럼 / 함수 상세
  COSTS OFF,    -- cost 숨김 (clean diff)
  FORMAT JSON   -- 도구 분석
) ...

시각화 도구

도구비고
explain.depesz.com색상 코딩
pev2 (Dalibo)tree 시각화
pg_stat_statements누적 통계
auto_explain느린 쿼리 자동 로깅

흔한 함정

WARNING

  1. EXPLAIN (without ANALYZE) 만 신뢰 = optimizer 의 추정. 실제 행동은 다를 수 있다.
  2. actual rowsrows= 차이 무시 = stale 통계. ANALYZE 필요.
  3. Hash Join큰 memory = work_mem 부족 시 disk spill. 그래도 Nested Loop 보다 빠를 수 있다.
  4. planner hint 부재 = PostgreSQL 은 공식 hint 없음. SET 변수로 간접 제어. (MySQL 은 STRAIGHT_JOIN 등 hint 직접.)

관련 위키

이 글의 용어 (6개)
[DB Internals] B-Tree와 B+Tree 인덱싱database-internals
정의 B-Tree (B+ Tree) 는 거의 모든 RDB 의 인덱스 자료구조. balanced tree, 디스크 I/O 최소화 설계. 특징: - 균형 (balanced): 모든 …
[DB Internals] MVCC: Multi-Version Concurrency Controldatabase-internals
정의 MVCC (Multi-Versioning Concurrency Control) 는 트랜잭션 간 격리를 여러 버전의 row 로 구현. 동시 read / write 가 락 없이…
[DB] GIN / GiST / Hash / BRIN: 비-B-tree 인덱스database-internals
정의 B-tree 외 PostgreSQL 의 특수 인덱스 들. 자세한 B-tree 는 참고. | 인덱스 | 적합 | |---|---| | B-tree | = < > BETWEEN…
[DB] MySQL / InnoDB: clustered index, redo log, MVCCdatabase-internals
정의 MySQL + InnoDB (기본 스토리지 엔진) 의 clustered index + redo log 기반 행 기반 RDBMS. 웹 / SaaS 의 가장 흔한 backbon…
[DB] PostgreSQL: 프로세스 모델, MVCC, WAL, 확장성database-internals
정의 PostgreSQL 은 오픈소스 ORDBMS. 1986 UC Berkeley POSTGRES 의 후예. MVCC, 확장 가능 타입, JSONB, full-text searc…
[DB] Transaction Isolation Levels: 완벽 가이드database-internals
정의 Transaction Isolation Level (트랜잭션 격리 수준) = 동시에 실행되는 여러 트랜잭션이 서로의 변경을 어디까지 볼 수 있는지 규정하는 수준. ACID …

💬 댓글

사이트 검색 / 명령어

검색

스크롤 = 확대/축소 · 드래그 = 이동 · 0 = 원래 크기 · ESC = 닫기