Notin
title: 2026-03-12 author: 강병호 (이름) date: 2026-03-12 (날짜) category: TIL/강병호/2026/03 (파일 경로 : TIL/{이름}/{연}/{월}) layout: post (자유) —
1. NOT IN 쿼리의 주요 문제점
전체 스캔 유발 (Full Table Scan): NOT IN은 부정형 조건이기 때문에 DBMS가 인덱스를 효율적으로 사용하기 어렵습니다. 대량의 데이터를 하나씩 대조하며 제외해야 하므로, 대부분 인덱스 레인지 스캔 대신 전체 테이블을 읽는 방식을 선택하여 성능이 급격히 저하됩니다.
인덱스 활용도 저하: IN 절은 인덱스를 통한 수직적 탐색이 가능하지만, NOT IN은 인덱스 내의 거의 모든 리프 블록을 읽어야 하는 인덱스 풀 스캔(Index Full Scan)을 유발하는 경우가 많습니다.
NULL 값 처리의 함정 (3-valued Logic): 비교 대상 리스트에 NULL이 하나라도 포함되어 있다면, 전체 결과가 Unknown으로 평가되어 아무런 레코드도 반환되지 않는 논리적 오류가 발생할 수 있습니다.
파싱 오버헤드: IN 절에 들어가는 인자(Parameter)의 개수가 수천 개 이상으로 많아질 경우, SQL 파싱 단계에서 시간이 오래 걸리고 옵티마이저의 실행 계획 수립에 부하를 줍니다.
2. 최적화 방안 및 대안 쿼리
방안 1: NOT EXISTS 활용 (권장) NOT EXISTS는 메인 쿼리의 각 행에 대해 서브쿼리의 조건이 만족하는지 확인하며, 일치하는 첫 번째 레코드를 찾는 즉시 스캔을 중단합니다.
SELECT p
FROM Post p
WHERE NOT EXISTS (
SELECT 1
FROM Post temp
WHERE temp.id = p.id AND temp.id IN :postIds
)
특징: 세미 조인(Semi-join) 최적화를 통해 ‘존재하지 않음’을 빠르게 판단합니다.
장점: NULL 값에 영향을 받지 않으며, 대규모 데이터셋에서 가장 안정적인 성능을 보입니다.
방안 2: LEFT JOIN + IS NULL 패턴 두 테이블을 조인한 후, 조인에 실패한(오른쪽 테이블의 값이 NULL인) 데이터만 필터링하는 방식입니다.
SQL SELECT p FROM Post p LEFT JOIN ( SELECT id FROM Post WHERE id IN :postIds ) filtered ON p.id = filtered.id WHERE filtered.id IS NULL 특징: 조인 구조로 변경하여 DBMS가 조인 알고리즘(Nested Loop, Hash Join 등)을 활용할 수 있게 합니다.
장점: 제외할 데이터가 상대적으로 적고, 조인 키에 인덱스가 잘 잡혀 있을 때 매우 효율적입니다.
성능과 논리적 정확성(NULL 처리) 측면에서 NOT IN 가장 유리합니다. 그러나 데이터가 적을 때, NOT IN을 사용해도 무방하지만, 데이터 증가 가능성을 고려하면 습관적으로 NOT EXISTS를 사용하는 것이 좋습니다.
성능 검증: 반드시 EXPLAIN 또는 EXPLAIN ANALYZE 명령어를 통해 실행 계획 상에서 Full Table Scan이 발생하는지, 조인 방식이 적절한지 확인해야 합니다.