쿼리 성능 개선 작업을 진행하면서 행의 값의 존재 유무를 표기하는데, EXISTS와 COUNT로 혼용하여 쿼리 가 되어 있는 부분이 있어 정리하면서 글로 남겨보았습니다.

결론부터 이야기 하면 행의 값의 존재 유무를 표기하는데  COUNT보다는 EXISTS가 성능이 더 좋습니다.

왜 그런지 비교와 예시를 들어 이유를 정리해 보겠습니다.

 

1. EXISTS vs COUNT 성능 비교

항목 EXISTS COUNT
목적 레코드 존재 여부 확인 데이터의 건수 확인
동작 방식 첫 번째 행 발견 시 즉시 중단 모든 조건 만족 행을 전부 탐색
성능 빠름 (항상 일정하게 빠름) 느림 (데이터 많을수록 느려짐)
용도 데이터 존재여부 판단 정확한 레코드 수가 필요할 때만

 

이론상 성능 비교만 봐도 존재 여부만을 생각할때 COUNT보다는 EXISTS가 빠르다는걸 알 수 있습니다.

EXISTS는 값의 존재 유무에따라 즉각 중단되지만, COUNT는 전제 데이터를 스캔하는 것을 알 수 있습니다.

실제 오라클에서 100만건의 더미 데이터를 통해 성능 확인을 해보겠습니다.

-- 테이블 생성
CREATE TABLE TB_BOARD (
    SEQ     NUMBER PRIMARY KEY,
    TITLE   VARCHAR2(200),
    CONTENT CLOB,
    WRITER  VARCHAR2(100)
);

-- 시퀀스 생성
CREATE SEQUENCE SQ_BOARD
START WITH 1
INCREMENT BY 1
NOCACHE
NOCYCLE;

-- 테스트 더미 데이터 100만건 입력
BEGIN
    FOR i IN 1..1000000 LOOP
        INSERT INTO TB_BOARD (
            SEQ, TITLE, CONTENT, WRITER
        ) VALUES (
            SQ_BOARD.NEXTVAL,
            '제목 ' || i,
            '내용입니다. 이건 테스트용 더미 데이터입니다. 번호: ' || i,
            '작성자' || MOD(i, 10)
        );
        
        -- 커밋 주기를 정해서 성능 최적화
        IF MOD(i, 10000) = 0 THEN
            COMMIT;
        END IF;
    END LOOP;
    COMMIT;
END;

 

위와 같이 테이블을 생성하고 100만건의 데이터를 입력 하겠습니다.


2. EXISTS와 COUNT 예시 쿼리 및 실행 계획 비교

-- 실행 속도 : 0.003초
SELECT 'Y'
FROM dual
WHERE EXISTS (
    SELECT 1
    FROM TB_BOARD
    WHERE WRITER = '작성자1'
);

EXISTS 실행 계획 결과

 

-- 실행 속도 0.053초
SELECT COUNT(*)
FROM TB_BOARD
WHERE WRITER = '작성자1';

COUNT 실행 계획 결과

 

둘다 쿼리를 실행 했을때는 1초 미만으로 쿼리가 빠르긴 하지만, 실행 속도와 실행 계획에서 COST를 보면 성능이 차이난다는 것을 알 수 있습니다. 그럼 여기서 성능을 더 높이기 위해서 WRITER행에 INDEX를 걸어 성능을 높여 보겠습니다.

-- 인덱스 생성
CREATE INDEX IDX_TB_BOARD_WRITER ON TB_BOARD(WRITER);
-- 인덱스 삭제
DROP INDEX IDX_TB_BOARD_WRITER;

INDEX 후 EXISTS 실행 계획 결과

 

INDEX 후 COUNT 실행 계획 결과

 

인덱스 후 결과는 COUNT는 성능이 향상 되었다는걸 알 수 있습니다. 하지만 EXISTS는 오히려 성능이 약간 떨어 졌다는 걸 알 수 있습니다. 간단히 이유를 정리 해봤습니다.

 

A. EXISTS는 인덱스를 꼭 안 쓸수도 있다.

EXISTS는 "조건을 만족하는 첫 번째 레코드만 찾으면 끝"이기 때문에,
풀 스캔이 더 빠르다고 판단되면 오라클 옵티마이저가 인덱스를 안 쓰고 풀 테이블 스캔으로 가기도 합니다.

그런데 WRITER에 인덱스를 추가하면, 옵티마이저가 인덱스 존재로 인해 인덱스 레인지 스캔을 하다가 오히려 더 많은 I/O와 비효율적인 랜덤 액세스로 성능이 나빠질 수 있습니다.

 

B. EXISTS는 "인덱스만 보고 끝내는" 작업이 아니다.

EXISTS 쿼리 안에서 SELECT 1 FROM TB_BOARD WHERE WRITER = '작성자1'라고 하면,

인덱스로 "WRITER = '작성자1'" 조건은 탐색하지만

결국 TB_BOARD 테이블의 실제 행(ROWID)을 따라가서 실제로 있는지 확인까지 해야 됩니다.

즉, "인덱스 스캔 → ROWID 읽기 → 테이블 접근" 이런 랜덤 I/O가 많아질 수 있어서 오히려 느려질 수 있습니다.

 

C. 반대로 COUNT(*)는 인덱스가 있으면 빠르게 인덱스만 보고 셈 가능

WRITER 인덱스를 쓰면 조건에 맞는 ROWID 목록을 순차적으로 얻어서, 굳이 전체 행을 읽지 않고 인덱스 레벨에서 COUNT가 가능하기 때문에 성능이 향상 되었습니다.


3. 정리

A. EXISTS

  • 조건을 만족하는 첫 번째 레코드만 찾으면 바로 TRUE 리턴하고 끝남.
  • 인덱스로 인해 오히려 성능이 떨어 질 수 있음.
  • 내부적으로 STOP 키워드처럼 작동.

B. COUNT

  • 조건에 맞는 모든 레코드를 스캔해서 카운트함.
  • 수백만 건이면 진짜 다 셈.
  • 결과가 많을수록 시간 증가.

존재 여부만 체크할 때는 반드시 EXISTS 사용 (예: 중복 체크, 데이터 유무 확인)

정확한 숫자가 필요할 때만 COUNT 사용 (예: 페이지네이션, 통계)

 

존재 여부만 알고 싶은데 COUNT로 다 세면 불필요한 리소스 낭비이고, EXISTS가 더 좋은 선택 입니다.

 

 

+ Recent posts