Search

REMINDER 02-02. 존재 여부 확인에 전체 COUNT를 사용하지 말자

상황: 회원 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였다. 각기 다른 실행 시점의 단일 측정값이므로 특정 방식이 항상 더 빠르다는 뜻은 아니다.