메인 콘텐츠로 건너뛰기

VillageSQL is a drop-in replacement for MySQL with extensions.

All examples on this page work on VillageSQL. Install Now →
서브쿼리와 JOIN은 종종 같은 결과를 만듭니다. 둘 중 무엇을 고르느냐는 가독성, NULL에 대한 정확성, 그리고 — 어떤 경우에는 — 성능에 영향을 줍니다.

서브쿼리의 종류

비상관 서브쿼리는 한 번 실행되고 그 결과를 외부 쿼리가 사용합니다:
상관 서브쿼리는 외부 쿼리의 열을 참조하므로 행마다 한 번씩 실행됩니다:
상관 서브쿼리는 큰 테이블에서 느릴 수 있습니다 — 서브쿼리가 외부 쿼리의 모든 행에 대해 실행됩니다. MySQL이 어떻게 실행하는지 보려면 EXPLAIN을 확인하세요.

IN 대 EXISTS 대 JOIN

이 3가지 패턴은 종종 같은 문제를 해결합니다:
MySQL 옵티마이저는 IN (subquery)를 내부적으로 세미 조인으로 다시 쓰는 경우가 많으므로, 성능 차이는 무시할 만한 경우가 잦습니다. 서브쿼리 결과 집합이 크면 EXISTS나 JOIN이 더 안정적입니다.

NOT IN의 NULL 문제

NOT IN은 서브쿼리가 NULL을 하나라도 반환하면 예상 밖으로 동작합니다:
orders.customer_id에 NULL 행이 하나라도 있으면 NOT IN 전체가 빈 결과를 반환합니다. 이것은 올바른 SQL 동작입니다 — NULL은 알 수 없는 값이므로, NOT IN (NULL, 1, 2)는 모든 비교에서 UNKNOWN입니다. 대신 NOT EXISTS를 사용하세요:
MySQL의 NULL 동작에 대한 자세한 내용은 MySQL의 NULL을 보세요.

FROM의 서브쿼리(파생 테이블)

FROM 절의 서브쿼리는 파생 테이블을 만듭니다:
이 패턴에는 보통 CTE가 더 명확합니다 — 로직이 인라인으로 중첩되지 않고 맨 위에서 이름을 갖고 정의됩니다. MySQL CTE가 파생 테이블보다 나을 때를 보세요.

성능 고려 사항

MySQL 옵티마이저는 많은 서브쿼리 패턴을 다시 씁니다. 직접 최적화하기 전에:
  1. 서브쿼리 버전과 JOIN 버전 모두에 EXPLAIN을 실행해서 MySQL이 실제로 무엇을 실행하는지 확인하세요
  2. 조인/상관 열에 인덱스가 있는지 확인하세요 — 보통 그것이 병목입니다
  3. EXPLAIN이 실행 계획에서 큰 차이를 보일 때만 다시 쓰세요

자주 묻는 질문

JOIN은 항상 서브쿼리보다 빠른가요?

꼭 그렇지는 않습니다. MySQL 8.0의 옵티마이저는 많은 경우 IN (subquery)를 세미 조인으로 다시 써서 명시적 JOIN과 같은 실행 계획을 만듭니다. 한 형태가 항상 더 빠르다고 가정하지 말고 EXPLAIN으로 측정하세요.

IN 대신 EXISTS를 언제 사용해야 하나요?

일치하는 데이터가 무엇인지가 아니라 일치가 존재하는지만 중요할 때, 서브쿼리가 NULL을 반환할 수 있을 때, 또는 서브쿼리가 큰 결과 집합을 반환할 때 EXISTS를 사용하세요. EXISTS는 첫 번째 일치에서 단락 평가하고, IN은 전체 목록을 만들어야 합니다.

세미 조인이 무엇인가요?

세미 조인은 오른쪽에 일치하는 행이 하나 이상 있는 왼쪽 테이블의 행을 반환하되, 여러 일치에 대해 왼쪽 테이블 행을 중복해서 만들지 않습니다. MySQL 옵티마이저는 IN (subquery)와 EXISTS를 효율적으로 실행하기 위해 내부적으로 세미 조인을 사용합니다. SQL에서 세미 조인을 직접 쓸 수는 없지만, EXISTS와 IN (subquery) 모두 옵티마이저가 세미 조인을 고려하게 만듭니다.

문제 해결

함께 보기