반응형
SQL 쿼리를 작성할 때 같은 결과를 얻기 위해 서브쿼리와 JOIN 중 어느 쪽을 선택해야 할지 고민한 적이 있을 것이다.
표현 방식은 달라도 결과는 비슷해 보이지만, 실제 실행 속도나 리소스 사용량은 상당히 다를 수 있다.
특히 데이터가 많아질수록 그 차이는 몇 배, 몇십 배까지 벌어진다.

🧪 테스트 환경 및 테이블 구조
두 개의 테이블이 있다고 가정하자.
CREATE TABLE users (
id INT PRIMARY KEY,
name VARCHAR(50)
);
CREATE TABLE orders (
id INT PRIMARY KEY,
user_id INT,
amount INT,
status VARCHAR(20)
);
- users: 약 10만 건
- orders: 약 50만 건
- orders.user_id에는 인덱스가 걸려 있음
반응형
✅ 테스트 1: 조건 필터용 서브쿼리 vs JOIN
서브쿼리 버전
SELECT name
FROM users
WHERE id IN (
SELECT user_id FROM orders WHERE amount > 100000
);
- 실행 시간: 2.4초
- EXPLAIN 결과: type = index_subquery, rows = 50,000
- 옵티마이저가 일부 최적화를 시도하긴 하지만 여전히 느림
JOIN 버전
SELECT DISTINCT u.name
FROM users u
JOIN orders o ON u.id = o.user_id
WHERE o.amount > 100000;
- 실행 시간: 0.28초
- EXPLAIN 결과: type = ref, rows = 3,800
- JOIN이 직접 인덱스를 타고 order 테이블을 빠르게 탐색
결론: 동일한 결과를 반환하더라도 JOIN 방식이 훨씬 빠르게 실행되고, 인덱스를 효과적으로 활용한다.
✅ 테스트 2: 집계 쿼리에서의 성능 비교
서브쿼리 버전
SELECT name,
(SELECT COUNT(*) FROM orders o WHERE o.user_id = u.id) AS order_count
FROM users u;
- 실행 시간: 5.2초
- users row 수만큼 반복적으로 orders 테이블을 검색 (N+1 문제 발생)
JOIN + GROUP BY 버전
SELECT u.name, COUNT(o.id) AS order_count
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
GROUP BY u.id;
- 실행 시간: 0.61초
- 조인 후 group by로 한 번에 처리
결론: SELECT절에 서브쿼리를 쓰면 루프마다 실행되므로 매우 비효율적
가능한 경우 JOIN + GROUP BY로 바꾸는 것이 성능 면에서 훨씬 유리하다.
반응형
✅ 테스트 3: NOT IN vs LEFT JOIN + IS NULL
서브쿼리 (NOT IN)
SELECT name
FROM users
WHERE id NOT IN (
SELECT user_id FROM orders WHERE status = 'cancelled'
);
- 실행 시간: 3.7초
- NULL이 포함된 결과에서는 의도한 결과가 나오지 않을 수 있음
JOIN 방식
SELECT u.name
FROM users u
LEFT JOIN orders o ON u.id = o.user_id AND o.status = 'cancelled'
WHERE o.id IS NULL;
- 실행 시간: 0.89초
- 정확한 결과와 빠른 실행 속도 확보
결론: NOT IN은 성능 저하와 결과 정확성 문제를 모두 유발할 수 있음
JOIN + IS NULL 방식이 실무에서 더 안전하고 빠르다.
🧠 언제 JOIN이 더 유리할까?
- 조건에 맞는 row를 필터링할 때
- 집계 함수(COUNT, SUM, AVG 등)를 사용할 때
- 다른 테이블 데이터를 참조해야 할 때
- 반복적으로 실행되는 서브쿼리를 포함할 때
JOIN은 MySQL 옵티마이저가 조인 순서, 인덱스, 실행 전략을 유동적으로 선택할 수 있게 해주므로 최적화 가능성이 높다.
🚨 서브쿼리가 불리한 대표적 상황
- SELECT 절 안의 서브쿼리 → N+1 패턴
- WHERE 절 안의 상관 서브쿼리
- NOT IN 서브쿼리
- FROM절에 서브쿼리가 중첩되어 조인 대상이 되는 경우
반면, 단일 값 조회, 조건이 매우 단순한 경우에는 서브쿼리도 성능상 크게 문제되지 않는다.
결국 쿼리 작성 목적과 데이터 양, 인덱스 구성에 따라 최적의 방법을 선택해야 한다.
✅ 정리
- 서브쿼리와 JOIN은 같은 결과를 만들어도 성능은 전혀 다를 수 있다.
- JOIN은 인덱스를 직접 타기 때문에 대용량 데이터에 훨씬 유리하다.
- 서브쿼리는 가독성 면에서는 좋지만, 중첩이나 반복 사용 시 심각한 병목을 만들 수 있다.
- 실무에서는 JOIN을 기본으로 쓰고, 필요할 때만 서브쿼리를 보완적으로 사용하는 전략이 좋다.
- 항상 EXPLAIN으로 실행계획을 확인하고, rows와 Extra 항목까지 꼼꼼히 점검해야 한다.
🔗 공식 문서 참고
MySQL 8.0 Reference Manual - Subqueries
MySQL 8.0 Reference Manual - Join Optimization
반응형
'DB' 카테고리의 다른 글
| [MySQL] 파티셔닝(Partitioning) 개념과 사용 목적 총정리 📦 (0) | 2025.07.08 |
|---|---|
| [MySQL] 쿼리 리팩토링 전/후 실행계획으로 성능 변화 분석하기 🔍 (0) | 2025.07.08 |
| [MySQL] 복잡한 쿼리 리팩토링: 서브쿼리 → JOIN → CTE 변환 사례 분석 (0) | 2025.07.08 |
| [MySQL] CTE vs 서브쿼리 성능 비교 및 튜닝 포인트 🧠 (0) | 2025.07.08 |
| [MySQL] CTE(Common Table Expression) 개념과 성능 특성 (0) | 2025.07.08 |
| [MySQL] JOIN의 성능 원리와 최적화 전략 (0) | 2025.07.08 |
| [MySQL] 서브쿼리의 성능 특징과 사용 시 주의점 (0) | 2025.07.08 |
| [MySQL] 서브쿼리 vs JOIN vs CTE 기본 개념 비교 총정리 (2) | 2025.07.08 |