TECH/sql

[SQL 성능 최적화] EXISTS vs IN: 동작 원리부터 성능 차이까지 완벽 분석

런코리치 2026. 3. 1. 08:00

[SQL 성능 최적화] EXISTS vs IN: 성능 차이가 발생하는 근본 원인 분석

SQL 쿼리를 작성하다 보면 특정 조건에 맞는 데이터가 다른 테이블에 존재하는지 확인해야 할 때가 많습니다. 이때 가장 먼저 떠올리는 것이 IN과 EXISTS입니다. "EXISTS가 더 빠르다"라는 말을 들어보셨을 수도 있지만, 최신 데이터베이스에서는 항상 그렇지도 않습니다.

오늘은 이 두 연산자의 내부 동작 원리를 비교하고, 어떤 상황에서 성능 차이가 발생하는지 근본적인 원인을 분석해 보겠습니다.


1. IN 연산자의 동작 원리

IN 연산자는 주로 서브쿼리의 결과 집합을 먼저 생성하고, 그 결과 집합에 메인 쿼리의 값이 포함되어 있는지 확인하는 방식으로 동작합니다.

  • 동작 방식: 서브쿼리 실행 → 결과 집합(Temporary Table/Memory) 생성 → 메인 쿼리 값과 비교.
  • 특징: 서브쿼리가 먼저 실행되어 상수가 된 결과를 가지고 메인 쿼리를 필터링하는 느낌입니다.
SQL
SELECT * FROM employees
WHERE department_id IN (SELECT department_id FROM departments WHERE location_id = 1000);

2. EXISTS 연산자의 동작 원리

EXISTS는 서브쿼리에 해당하는 데이터가 단 한 건이라도 있는지 확인하는 순간 탐색을 멈춥니다. 이를 '세미 조인(Semi-Join)' 최적화라고 부릅니다.

  • 동작 방식: 메인 쿼리의 한 행을 집어듦 → 서브쿼리에 해당 행의 값이 있는지 확인 → 존재하면 True 반환 후 즉시 중단(Short-circuit).
  • 특징: 메인 쿼리의 결과에 따라 서브쿼리가 실행되므로, 서브쿼리가 메인 쿼리의 컬럼을 참조하는 '상관 서브쿼리' 형태를 띠는 경우가 많습니다.
SQL
SELECT * FROM employees e
WHERE EXISTS (SELECT 1 FROM departments d WHERE d.department_id = e.department_id AND d.location_id = 1000);

3. 성능 차이가 발생하는 근본적인 원인

① NULL 처리의 함정 (가장 중요한 차이)

IN과 EXISTS의 가장 큰 차이는 NULL값에 대한 논리적 처리에 있습니다.

  • IN: 서브쿼리 결과에 NULL이 포함되어 있으면, NOT IN의 경우 결과가 아예 나오지 않는 등 논리적 오류가 발생할 수 있습니다. 이를 처리하기 위해 옵티마이저는 추가적인 체크 로직을 수행하며, 이 과정에서 성능이 저하될 수 있습니다.
  • EXISTS: 단순히 조건에 맞는 행이 있는지(Existence)만 보므로, 특정 컬럼의 NULL 여부에 관계없이 논리적으로 명확하며 연산이 빠릅니다.

② 데이터의 분포와 크기

과거 옵티마이저 기준으로는 다음과 같은 공식이 통용되었습니다.

  • 서브쿼리 결과가 작을 때: IN이 유리 (결과를 미리 뽑아 상수로 비교하므로)
  • 메인 쿼리 결과가 작고 서브쿼리 데이터가 많을 때: EXISTS가 유리 (인덱스를 통해 존재 여부만 빠르게 확인하므로)

4. 최신 DBMS의 트렌드: 서브쿼리 언네스팅(Unnesting)

과거에는 IN과 EXISTS를 엄격히 구분해서 써야 했지만, 최신 Oracle이나 MySQL 8.0+, PostgreSQL의 옵티마이저는 매우 똑똑해졌습니다.

개발자가 IN을 쓰든 EXISTS를 쓰든, 옵티마이저는 내부적으로 이를 가장 효율적인 세미 조인(Semi-Join) 형태로 변환(Unnesting)하여 실행 계획을 수립합니다. 즉, 대부분의 일반적인 상황에서는 두 연산자의 성능 차이가 거의 나지 않습니다.


5. 실무에서의 선택 기준

성능 차이가 줄어들었다고는 하지만, 여전히 우리는 전략적인 선택을 해야 합니다.

  1. 가독성: IN은 직관적이고 코드가 깔끔합니다. 단순 필터링에는 IN을 사용하세요.
  2. 복잡한 조건: 여러 컬럼을 비교하거나 복잡한 상관관계가 필요할 때는 EXISTS가 논리적으로 더 명확합니다.
  3. 대량 데이터의 NOT 조건: NOT IN 보다는 NOT EXISTS를 사용하는 것이 성능과 NULL 처리 측면에서 훨씬 안전하고 빠릅니다. (가장 강력히 권장하는 팁입니다.)

🏁 마무리하며

EXISTS와 IN의 성능 논쟁은 결국 "데이터가 어떻게 분포되어 있고, 인덱스가 어떻게 걸려 있는가"로 귀결됩니다.

무조건 어떤 것이 빠르다는 맹신보다는, 대량의 데이터를 다룰 때는 반드시 EXPLAIN 명령어를 통해 실행 계획을 확인하는 습관을 들여야 합니다. 옵티마이저가 내 의도대로 세미 조인을 수행하고 있는지, 인덱스를 적절히 타고 있는지 확인하는 것이 진정한 성능 최적화의 시작입니다.