안녕하세요, 백엔드 개발자입니다.
백엔드 로직을 구현하다 보면, 특정 조건에 맞는 데이터가 '존재하는지' 여부만 확인하면 되는 경우가 많습니다. 예를 들면 다음과 같은 상황입니다.
- 사용자가 닉네임 중복 확인을 요청했을 때
- 게시글에 좋아요를 눌렀는지 여부를 확인할 때
이런 경우, 가장 먼저 떠오르는 방법은 COUNT(*)를 사용하는 것입니다.
SELECT COUNT(*)
FROM users
WHERE nickname = 'supiz';
이 쿼리의 결과가 0보다 크면 '존재한다', 0이면 '존재하지 않는다'고 판단하는 방식이죠.
매우 직관적이고 저 또한 오랫동안 습관적으로 사용해왔습니다.
하지만 '존재 여부' 확인이 목적이라면, COUNT(*)는 성능상 비효율을 유발할 수 있습니다.
이번 글에서는 이런 상황에서 EXISTS를 사용하는 것이 왜 더 좋은 선택인지 알아보겠습니다.
1. COUNT(*)는 모든 것을 세어본다
COUNT(*)의 가장 큰 특징은 조건에 맞는 데이터를 모두 찾아서 그 개수를 센다는 점입니다.
위의 닉네임 중복 확인 쿼리를 예로 들어보겠습니다. nickname 컬럼에 인덱스가 걸려있다고 하더라도, 데이터베이스는 nickname = 'supiz'인 데이터를 끝까지 모두 스캔하여 총 몇 개인지 집계합니다.
우리가 원하는 것은 'supiz'라는 닉네임이 단 한 개라도 있는지, 즉 존재 여부인데도 말이죠. 데이터가 수백만 건이고 우연히 중복된 닉네임이 많다면, 이 단순한 확인 작업은 생각보다 많은 리소스를 사용하게 됩니다.
2. EXISTS는 하나만 찾으면 멈춘다
반면, EXISTS는 다른 방식으로 동작합니다. EXISTS는 서브쿼리(Subquery)와 함께 사용되며, 서브쿼리에서 단 한 개의ロウ(row)라도 반환되면 즉시 TRUE를 반환하고 쿼리 실행을 멈춥니다.
-- 'supiz' 닉네임이 존재하는지 확인
SELECT EXISTS (
SELECT 1
FROM users
WHERE nickname = 'supiz'
);
* EXISTS 서브쿼리 안의 SELECT 절에는 1이나 * 등 어떤 것을 써도 성능에 차이가 없습니다. 중요한 것은 조건에 맞는 데이터가 있느냐 없느냐 뿐입니다.
EXISTS는 COUNT(*)처럼 모든 데이터를 스캔할 필요가 없습니다. 조건에 맞는 데이터를 딱 하나만 찾으면, 그 즉시 "데이터가 존재한다"고 판단하고 자신의 임무를 끝냅니다.
3. 언제 무엇을 써야 할까?
그렇다면 COUNT(*)는 항상 나쁜 것일까요? 물론 아닙니다. 두 키워드는 목적에 맞게 사용하는 것이 중요합니다.
- COUNT(*)를 사용해야 할 때
- 정확한 개수가 필요할 때
- 예: "게시물의 총 댓글 수: 123개", "전체 회원 수: 5,432명"
- 목록 조회 API에서 전체 아이템 개수를 알려주어 페이지네이션을 구현할 때
- EXISTS를 사용해야 할 때
- 데이터의 존재 여부(Boolean)만 확인하면 될 때
- 예: "이 닉네임은 사용 가능한가요?", "이미 좋아요를 누른 게시물인가요?"
- IF문이나 CASE문처럼 조건 분기에 활용할 때
4. 정리하며
단순히 데이터의 존재 유무를 확인하는 로직이라면, 습관적으로 사용하던 COUNT(*) > 0 대신 EXISTS를 사용해 보는 것을 추천합니다. 사소해 보일 수 있는 이 변화가 데이터가 많은 테이블에서는 의미 있는 성능 개선으로 이어질 수 있습니다.
목적에 맞는 SQL 키워드를 선택하는 것은 안정적인 백엔드 시스템을 만드는 중요한 첫걸음입니다.
긴 글 읽어주셔서 감사합니다.
'Backend' 카테고리의 다른 글
| OFFSET 페이지네이션 개선하기: 커서 기반 페이지네이션 도입 (0) | 2025.09.02 |
|---|---|
| 목록 조회 API, OFFSET 페이지네이션 괜찮을까? (0) | 2025.09.01 |
| 나만의 데코레이터 만들기: 실전 기능 구현 (0) | 2025.08.29 |
| 나만의 데코레이터 만들기: NestJS에서 반복적인 API 설정 줄이기 (0) | 2025.08.27 |
| 서버 부하를 줄이는 이미지 업로드 전략 (3편): 유연한 썸네일 URL 관리 패턴 (0) | 2025.08.24 |
