결론부터 말씀드리면, "매번 실행되지만 JOIN보다 훨씬 효율적인 경우가 많다" 입니다.
중첩 서브쿼리를 사용하는 EXISTS는 메인 쿼리의 행 수(N)만큼 서브쿼리가 실행되는 것이 맞습니다. 하지만 성능이 저하될 것이라는 예상은 두 가지 중요한 사실을 간과하고 있는 것이다.
EXISTS의 동작: 서브쿼리에서 조건을 만족하는 단 하나의 행이라도 찾으면 즉시 평가를 멈추고 TRUE를 반환한 뒤 다음 메인 쿼리 행으로 넘어갑니다. 서브쿼리 테이블을 끝까지 스캔할 필요가 전혀 없습니다.
JOIN의 동작: JOIN은 조건을 만족하는 모든 행을 찾아야 합니다. 예를 들어, 고객 한 명에게 1,000개의 주문이 있다면, JOIN은 그 1,000개의 조합을 모두 만들어 메모리에 올리려고 시도합니다.
어떤 학생이 도서관에 책을 한 권이라도 빌렸는지 확인하고 싶습니다.
EXISTS 방식: 대출 기록을 한 줄씩 보다가 그 학생 이름이 한 번이라도 나오면 "네, 빌렸네요!" 하고 확인을 끝냅니다. (매우 빠름)
JOIN 방식: 그 학생이 빌린 책 목록 전체를 찾아서 가져옵니다. (상대적으로 느림)
특히 서브쿼리의 WHERE 절에 있는 조건(o.customer_id = c.customer_id)에 인덱스(Index)가 잘 설정되어 있다면, EXISTS는 매번 실행될 때마다 인덱스를 통해 번개처럼 빠르게 단 하나의 행만 찾고 바로 종료됩니다. 따라서 N번 실행되더라도 그 각각의 실행 비용이 거의 '0'에 가깝기 때문에 전체적으로는 매우 빠릅니다.
반면, JOIN은 두 테이블의 데이터를 실제로 결합하기 위해 양쪽 테이블에서 데이터를 읽어와야 하므로 더 많은 I/O 비용과 메모리를 사용합니다.
EXISTS는 애초에 중복을 만들지 않으므로 이러한 추가 비용이 전혀 발생하지 않습니다.
이 질문에 대한 답은 쿼리의 목적에 따라 명확하게 나뉩니다.
EXISTS를 써야 할 때:
다른 테이블의 데이터가 필요한 것이 아니라, 단지 특정 조건을 만족하는 데이터가 존재하는지 여부만 확인하고 싶을 때. (가장 중요한 기준)
"~한 데이터를 가진 A 테이블을 조회해줘" 와 같은 요구사항일 때.
JOIN을 써야 할 때:
SELECT 절이나 WHERE 절에서 다른 테이블의 컬럼 값을 직접 사용해야 할 때.
"A 테이블과 B 테이블의 컬럼을 합쳐서 보여줘" 와 같은 요구사항일 때.
따라서 'EXISTS는 JOIN보다 항상 느리다'는 것은 대표적인 오해 중 하나입니다. 오히려 '존재' 여부를 묻는 쿼리에서는 JOIN을 남용하는 것이 심각한 성능 저하를 일으키는 안티 패턴(Anti-Pattern)이 될 수 있습니다.