Home

(MySQL/MariaDB 실무 튜닝) 서브쿼리보다 JOIN이 더 빠른 진짜 이유, 실행 계획으로 깊이 파헤치기

Published in mysql_maria
December 11, 2025
3 min read
(MySQL/MariaDB 실무 튜닝) 서브쿼리보다 JOIN이 더 빠른 진짜 이유, 실행 계획으로 깊이 파헤치기

안녕하세요, 20년 넘게 MySQL과 MariaDB의 깊은 바다를 항해해온 ‘코딩하는곰’입니다. 오늘은 수많은 개발자 분들이 궁금해하시고, 실무에서 가장 흔히 마주치는 성능 이슈 중 하나인 “서브쿼리와 JOIN, 도대체 어떤 것이 더 빠를까?”라는 질문에 대해, 단순한 감이 아닌 MySQL 옵티마이저의 실행 계획(EXPLAIN) 을 통해 근본부터 파헤쳐보려고 합니다. “JOIN이 더 빠르다”는 말은 들어봤지만, 정확히 그런지, 어떤 경우에 그런지 명확히 알고 계신가요? 이번 포스팅을 통해 여러분의 쿼리 작성 실력과 문제 해결 능력을 한 단계 업그레이드시켜 드리겠습니다.

1. 서브쿼리가 JOIN으로 변환되는 과정과 옵티마이저의 한계

많은 분들이 오해하는 부분이 있습니다. 현대의 MySQL/MariaDB 옵티마이저는 매우 똑똑해서, 단순한 서브쿼리(주로 비상관 서브쿼리)를 자동으로 JOIN 연산으로 쿼리 재작성(Query Rewrite) 을 시도합니다. 이론적으로는 이렇게 최적화된 경우 둘의 성능이 비슷해질 수 있습니다. 하지만, 옵티마이저의 변환 로직이 항상 완벽하지는 않습니다. 특히 다음과 같은 복잡한 경우에는 서브쿼리를 최적의 JOIN 형태로 바꾸지 못하고, 비효율적인 실행 계획을 세울 수 있습니다.

  • 상관 서브쿼리(Correlated Subquery): 서브쿼리가 외부 쿼리의 컬럼을 참조할 때. 이는 마치 프로그래밍의 ‘Nested Loop’처럼 동작하여 외부 쿼리의 매 행마다 서브쿼리를 한 번씩 실행시킬 수 있습니다.
  • IN, NOT IN 절 내의 복잡한 서브쿼리
  • GROUP BY, 집계 함수가 포함된 서브쿼리
  • LIMIT 이 있는 서브쿼리 이때 옵티마이저가 선택하는 전략은 주로 임시 테이블(Temporary Table) 을 생성하는 것입니다. 서브쿼리의 결과를 먼저 임시 테이블로 물리화(Materialization)한 후, 외부 쿼리와 조인하는 방식이죠. 이 과정에서 큰 문제가 발생합니다.
-- 예시: 비효율을 유발할 수 있는 서브쿼리
SELECT e.name, e.department_id
FROM employees e
WHERE e.salary > (
SELECT AVG(salary)
FROM salaries s
WHERE s.year = 2023
);
-- 옵티마이저는 아마 salaries 테이블의 2023년 평균 급여를 계산해 임시 테이블에 저장한 후,
-- employees 테이블의每一行과 비교하는 계획을 세울 수 있습니다.

임시 테이블 생성의 문제점:

  1. 추가적인 디스크 I/O: 임시 테이블이 메모리(tmp_table_size, max_heap_table_size)를 초과하면 디스크에 저장되어 속도가 급격히 느려집니다.
  2. 인덱스 부재: 생성된 임시 테이블에는 기본적으로 적절한 인덱스가 존재하지 않습니다. 이후 조인 시 풀 스캔이 발생할 확률이 높습니다.
  3. 추가적인 생성 비용: 결과 집합을 계산하고 테이블로 만드는 작업 자체가 순수 CPU와 메모리 자원을 소모합니다.

(MySQL/MariaDB 실무 튜닝) 서브쿼리보다 JOIN이 더 빠른 진짜 이유, 실행 계획으로 깊이 파헤치기
(MySQL/MariaDB 실무 튜닝) 서브쿼리보다 JOIN이 더 빠른 진짜 이유, 실행 계획으로 깊이 파헤치기


📘 코딩 튜토리얼과 가이드를 원한다면, (자바 기초) 대입 연산자와 복합 대입 연산자 완벽 가이드 - 코딩하는곰를 참고해보세요.

2. 실행 계획(EXPLAIN)으로 확인하는 성능 차이의 본질

이론은 그렇다치고, 실제로 어떻게 확인할까요? 바로 EXPLAIN 명령어가 답입니다. EXPLAIN은 옵티마이저가 선택한 실행 계획을 보여주며, 여기서 서브쿼리와 JOIN의 근본적인 차이를 눈으로 확인할 수 있습니다. 서브쿼리를 사용했을 때 EXPLAIN 결과에서 주로 보게 되는 비효율적인 패턴들입니다.

  • DEPENDENT SUBQUERY: 가장 흔하면서도 위험한 유형입니다. “의존적인” 서브쿼리라는 뜻으로, 외부 쿼리의 각 행에 대해 서브쿼리가 반복 실행됨을 의미합니다. 데이터가 1만 건이면 서브쿼리가 1만 번 실행됩니다.
  • DERIVED (파생 테이블): FROM 절에 사용된 서브쿼리의 결과가 임시 테이블로 구체화되었음을 나타냅니다. 위에서 언급한 임시 테이블 생성의 모든 단점을 내포하고 있습니다.
  • UNCACHEABLE SUBQUERY: 결과를 캐시할 수 없는 서브쿼리로, 매번 실행 비용이 발생합니다. 반면, 잘 작성된 JOIN 쿼리의 EXPLAIN 결과는 훨씬 깔끔합니다.
  • SIMPLE: 단순 SELECT를 나타내며, 서브쿼리 없이 조인만 사용되었을 때 보입니다.
  • 조인 순서와 타입: type 컬럼에서 eq_ref(고유 인덱스 조인), ref(비고유 인덱스 조인) 같은 효율적인 조인 타입을 확인할 수 있습니다. ALL(풀 테이블 스캔)이 보인다면 인덱스 검토가 필요합니다.
  • 인덱스 활용: key 컬럼을 통해 실제 사용된 인덱스를 명확히 볼 수 있습니다.
-- 위 서브쿼리를 JOIN으로 재작성한 예시
SELECT e.name, e.department_id
FROM employees e
CROSS JOIN (
SELECT AVG(salary) as avg_sal
FROM salaries
WHERE year = 2023
) avg_table
WHERE e.salary > avg_table.avg_sal;
-- EXPLAIN으로 비교해보세요!
-- 서브쿼리 버전: 아마 derived table (avg_table) 생성 후 조인
-- JOIN 버전: 더 직관적인 실행 계획을 보여줄 가능성이 높습니다.

직접 비교 실습 해보기: 여러분의 실제 쿼리를 서브쿼리 버전과 JOIN 버전 두 가지로 작성한 후, EXPLAIN을 실행하고 rows(예상 행수), Extra 정보(Using temporary, Using filesort 등)를 꼼꼼히 비교해보세요. EXPLAIN ANALYZE(MySQL 8.0+, MariaDB 10.1+)를 사용하면 실제 실행 시간까지 측정할 수 있어 더욱 명확합니다.

(MySQL/MariaDB 실무 튜닝) 서브쿼리보다 JOIN이 더 빠른 진짜 이유, 실행 계획으로 깊이 파헤치기
(MySQL/MariaDB 실무 튜닝) 서브쿼리보다 JOIN이 더 빠른 진짜 이유, 실행 계획으로 깊이 파헤치기


카페나 공공장소에서 와이파이를 이용할 때, 내 위치 정보가 어떻게 노출되는지 아이피로 위치 확인 서비스를 통해 확인해보세요.

3. JOIN이 더 빠를 수밖에 없는 구조적 이유와 실무 적용 팁

지금까지의 분석을 종합하면, JOIN이 서브쿼리보다 선호되는 이유는 데이터베이스 엔진의 본질에 더 부합하기 때문입니다.

  1. 집합적 사고와 일괄 처리: RDBMS는 집합 기반으로 설계되었습니다. JOIN은 여러 테이블을 하나의 논리적 집합으로 연결하는 데 최적화된 연산입니다. 반면, 상관 서브쿼리는 절차적 사고(루프)에 가까워 엔진의 강점을 살리기 어렵습니다.
  2. 인덱스 활용의 최적화: 옵티마이저는 JOIN을 처리할 때 조인 키(Join Key)에 생성된 인덱스를 최대한 활용할 수 있는 경로를 찾습니다. 반면 서브쿼리 결과로 생성된 임시 테이블에는 인덱스가 없어, 이후 작업이 항상 비효율적일 수밖에 없습니다.
  3. 파이프라인 처리 가능성: 이상적인 JOIN은 중간 결과를 모두 물리화하지 않고도 처리(파이프라이닝)가 가능합니다. 서브쿼리의 임시 테이블 생성은 이러한 흐름을 끊어버립니다.
  4. 옵티마이저의 예측 가능성: JOIN은 일반적으로 더 표준화되고 예측 가능한 패턴을 제공합니다. 옵티마이저가 최적의 조인 순서(Join Order)와 알고리즘(Nested Loop, Hash Join, Sort-Merge Join)을 선택할 수 있는 여지가 훨씬 큽니다. 실무에서 바로 써먹는 코딩하는곰의 조언:
  • 법칙이 아닌 원칙: “무조건 JOIN만 써라”가 아닙니다. 단순한 스칼라 서브쿼리나, 가독성이 극단적으로 뛰어난 경우는 서브쿼리를 사용해도 좋습니다. 하지만 성능이 중요한 핵심 로직이나 대량 데이터를 다루는 쿼리에서는 JOIN을 먼저 고려하세요.
  • EXPLAIN은 나의 친구: 쿼리를 작성했으면 EXPLAIN으로 실행 계획을 확인하는 습관을 들이세요. DEPENDENT SUBQUERYDERIVED가 보인다면 즉시 JOIN으로 리팩토링할 신호입니다.
  • 인덱스는 왕도: JOIN이든 서브쿼리든, 조인 조건(ON 절)과 필터 조건(WHERE 절)에 사용된 컬럼에 적절한 인덱스가 있는지가 모든 성능의 핵심입니다. 인덱스 없이는 최적화의 한계가 명확합니다.
  • MariaDB 10.2+ / MySQL 8.0+의 윈도우 함수 활용: RANK(), LAG(), LEAD() 등 윈도우 함수를 사용하면 서브쿼리를 사용해야 했던 복잡한 분석 쿼리를 훨씬 효율적으로 작성할 수 있습니다.

(MySQL/MariaDB 실무 튜닝) 서브쿼리보다 JOIN이 더 빠른 진짜 이유, 실행 계획으로 깊이 파헤치기
(MySQL/MariaDB 실무 튜닝) 서브쿼리보다 JOIN이 더 빠른 진짜 이유, 실행 계획으로 깊이 파헤치기


💡 건강을 위한 식단에 도움을 줄 수 있는 정보는 바로, 눈건강 트리플케어 플러스를 참고해보세요.

정리하자면, 서브쿼리가 JOIN보다 느린 근본적인 이유는 옵티마이저의 최적화 한계, 불필요한 임시 테이블 생성, 그리고 인덱스 활용의 실패에 있습니다. 이는 단순한 속담이 아니라, 데이터베이스 엔진의 동작 원리와 실행 계획이라는 객관적인 도구를 통해 확인할 수 있는 사실입니다. 여러분도 이제 EXPLAIN이라는 강력한 현미경을 손에 쥐었습니다. 다음번에 쿼리 성능에 의문이 생기면, 추측하지 마시고 실행 계획을 직접 들여다보세요. 그 안에 모든 답이 있습니다. 쿼리 하나를 개선하는 것이 수천, 수만 번 실행되었을 때 누적되는 성능 향상과 비용 절감은 상상 이상입니다. 오늘도 데이터와의 싸움(?)에서 한 발 앞서 나가시는 여러분이 되시길 바랍니다. 궁금한 점이나 더 다루었으면 하는 주제가 있다면 언제든지 댓글로 남겨주세요. 다음 포스팅에서 또 만나요! 코딩하는곰이었습니다.

최근 당첨번호와 통계를 한눈에 보고 싶다면, AI 번호 추천과 QR코드 확인이 가능한 지니로또AI를 설치해보세요.









최상의 건강을 위한 영양가득한 식품과 정보! life-plus.co.kr 바로가기
최상의 건강을 위한 영양가득한 식품과 정보! life-plus.co.kr 바로가기



다채로운 문화축제와 공연 소식을 공유하는 블로그! culturestage.co.kr 바로가기
다채로운 문화축제와 공연 소식을 공유하는 블로그! culturestage.co.kr 바로가기



비트코인 세계로의 첫걸음! 지금 가입하고 거래 수수료 할인 혜택 받으세요! bitget.com 바로가기
비트코인 세계로의 첫걸음! 지금 가입하고 거래 수수료 할인 혜택 받으세요! bitget.com 바로가기




Tags

#developer#coding#mysql_maria

Share

Previous Article
자바스크립트 스프레드 문법 완벽 가이드 객체와 배열 복사의 모든 것

Related Posts

MySQL/MariaDB 필수 통계 함수 완벽 가이드 COUNT, SUM, AVG 활용법과 실무 예제
December 31, 2025
3 min