DB

[MySQL] 서브쿼리 vs JOIN 실전 성능 비교 예제

인생아 2025. 7. 8. 16:57
반응형

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

 

반응형