상황: 회원 M0035의 주문이 하나라도 존재하는지 확인한다. 해당 회원의 주문은 2,150건이며, mbr_id에는 tr_ord_fk01 인덱스가 있다.
BAD SQL
EXPLAIN (ANALYZE, BUFFERS, COSTS OFF)
SELECT COUNT(*) > 0 AS has_order
FROM startdbpg.tr_ord o
WHERE o.mbr_id = 'M0035';
SQL
복사
실측 실행계획
Aggregate (actual time=1.122..1.122 rows=1 loops=1)
Buffers: shared hit=72
-> Index Only Scan using tr_ord_fk01 on tr_ord o (actual time=0.173..0.926 rows=2150 loops=1)
Index Cond: (mbr_id = 'M0035'::text)
Heap Fetches: 67
Buffers: shared hit=72
Planning:
Buffers: shared hit=116
Planning Time: 0.757 ms
Execution Time: 1.285 ms
Plain Text
복사
•
COUNT(*) > 0는 존재 여부만 필요해도 조건에 맞는 2,150건을 모두 읽은 후 개수를 계산한다.
•
실측 결과 Index Only Scan의 rows=2150, Buffers: shared hit=72로 불필요한 읽기 작업이 발생했다.
GOOD SQL
EXPLAIN (ANALYZE, BUFFERS, COSTS OFF)
SELECT EXISTS (
SELECT 1
FROM startdbpg.tr_ord o
WHERE o.mbr_id = 'M0035'
) AS has_order;
SQL
복사
실측 실행계획
Result (actual time=0.098..0.098 rows=1 loops=1)
Buffers: shared hit=4
InitPlan 1
-> Index Only Scan using tr_ord_fk01 on tr_ord o (actual time=0.096..0.096 rows=1 loops=1)
Index Cond: (mbr_id = 'M0035'::text)
Heap Fetches: 0
Buffers: shared hit=4
Planning:
Buffers: shared hit=110
Planning Time: 0.580 ms
Execution Time: 0.205 ms
Plain Text
복사
•
EXISTS는 일치하는 행을 처음 발견하면 존재 여부를 판단하고 검색을 끝낼 수 있다.
•
이번 실측에서는 rows=1, Buffers: shared hit=4로 읽기 작업을 줄였다.
GOOD SQL — 다른 방법: LIMIT 1
EXPLAIN (ANALYZE, BUFFERS, COSTS OFF)
SELECT 1 AS has_order
FROM startdbpg.tr_ord o
WHERE o.mbr_id = 'M0035'
LIMIT 1;
SQL
복사
실측 실행계획
Limit (actual time=8.954..8.955 rows=1 loops=1)
Buffers: shared hit=4
-> Index Only Scan using tr_ord_fk01 on tr_ord o (actual time=8.952..8.953 rows=1 loops=1)
Index Cond: (mbr_id = 'M0035'::text)
Heap Fetches: 0
Buffers: shared hit=4
Planning:
Buffers: shared hit=167
Planning Time: 21.620 ms
Execution Time: 9.049 ms
Plain Text
복사
•
LIMIT 1도 조건에 맞는 첫 행을 찾으면 검색을 종료한다. 이번 실측에서는 rows=1, Buffers: shared hit=4였다.
•
EXISTS는 항상 true 또는 false 한 행을 반환한다. 반면 이 LIMIT 1 예제는 존재하면 1행, 없으면 0행을 반환하므로 애플리케이션에서 결과 행 유무를 확인해야 한다.
핵심
•
데이터의 존재 여부만 필요하다면 전체 COUNT(*) 대신 EXISTS 또는 LIMIT 1을 사용하자.
•
Boolean 결과가 필요하면 EXISTS가 간결하며, 행의 존재 여부만 확인하면 LIMIT 1도 유효하다. 필요한 결과만 얻고 검색을 종료하는 습관이 중요하다.
참고
•
정확한 건수가 필요한 경우에는 COUNT(*)를 사용해야 한다.
•
일치하는 행이 없거나 매우 적으면 방법 간 성능 차이가 작을 수 있다. 접근 방식도 실행계획에 따라 달라진다.
•
BAD의 Index Only Scan에서 Heap Fetches: 67이 발생했다. Index Only Scan이라도 Visibility Map 상태에 따라 테이블 확인이 필요할 수 있다.
•
실측 실행시간은 COUNT 1.285ms, EXISTS 0.205ms, LIMIT 1 9.049ms였다. 각기 다른 실행 시점의 단일 측정값이므로 특정 방식이 항상 더 빠르다는 뜻은 아니다.


