MySQL에서 데이터를 결합하기 위해 가장 많이 쓰는 기능이 바로 JOIN이다.
특히 테이블이 정규화된 구조라면 거의 모든 쿼리에 JOIN이 들어간다고 봐도 무방하다.
하지만 JOIN을 잘못 사용하면 의도치 않은 N*N 조인, 풀 스캔, 임시 테이블 생성으로 인해 성능이 급격히 저하된다.

🧠 JOIN의 내부 동작 방식
MySQL 옵티마이저는 JOIN을 처리할 때 중첩 루프 조인(Nested Loop Join) 방식을 기본으로 사용한다.
즉, 한 테이블에서 각 row를 하나씩 읽으며 다른 테이블에 조건이 일치하는 row를 반복해서 찾는 구조다.
예시:
SELECT u.name, o.amount
FROM users u
JOIN orders o ON u.id = o.user_id;
이 쿼리는 내부적으로 다음과 같이 작동한다.
- users 테이블의 모든 row를 순차적으로 탐색
- 각 row마다 orders 테이블에서 user_id = u.id 조건으로 검색
- 일치하는 row가 있을 경우 결과에 포함
이 구조는 단순하지만 인덱스가 없을 경우 orders 테이블 전체를 매번 탐색하게 된다.
이로 인해 row 수가 많을수록 성능이 기하급수적으로 떨어지는 병목 지점이 발생한다.
🔎 EXPLAIN으로 JOIN 분석하기
EXPLAIN
SELECT u.name, o.amount
FROM users u
JOIN orders o ON u.id = o.user_id;
대표적으로 확인할 컬럼은 아래와 같다.
- type: ref 이상이어야 인덱스를 타는 구조
- key: 실제 사용된 인덱스
- rows: 조인 대상 row 수
- Extra: Using where, Using index, Using temporary 등 여부
type = ALL, Extra = Using temporary; Using filesort 조합은 성능 이슈 신호다.
⚙️ 성능 최적화를 위한 전략
✅ 1. 조인 조건 컬럼에 반드시 인덱스를 걸 것
ALTER TABLE orders ADD INDEX idx_user_id (user_id);
조인 조건이 되는 컬럼(user_id)에는 반드시 인덱스가 있어야 성능이 급격히 향상된다.
✅ 2. 필요한 컬럼만 SELECT
SELECT u.name, o.amount
불필요한 컬럼을 SELECT하는 경우 InnoDB에서 디스크 접근이 늘어나게 되며, 조인 성능에 악영향을 준다.
✅ 3. 조인 순서에 주의
MySQL은 기본적으로 옵티마이저가 조인 순서를 결정하지만, 복잡한 쿼리에서는 STRAIGHT_JOIN 키워드로 순서를 강제할 수도 있다.
SELECT STRAIGHT_JOIN u.name, o.amount
FROM users u
JOIN orders o ON u.id = o.user_id;
데이터 건수가 적은 테이블을 먼저 처리하는 것이 일반적으로 유리하다.
✅ 4. LEFT JOIN은 꼭 필요한 경우만 사용
LEFT JOIN은 모든 왼쪽 테이블 row를 유지하므로 쿼리 처리 비용이 높다.
조건에 따라 INNER JOIN으로 바꾸면 성능이 더 좋아질 수 있다.
✅ 5. 조인 수가 많은 경우 서브쿼리 분해도 고려
3개 이상의 테이블이 조인될 경우 옵티마이저가 잘못된 조인 순서를 선택하는 경우도 있다.
이럴 땐 복잡한 쿼리를 CTE나 서브쿼리로 쪼개고 각각 인덱스를 활용하도록 분리하는 것이 효과적이다.
🧪 실무 예제: 조인 튜닝 전/후
튜닝 전 쿼리:
SELECT * FROM orders o
JOIN users u ON o.user_id = u.id
WHERE u.status = 'active';
- 실행 시간: 4.8초
- EXPLAIN 결과: type = ALL, rows = 80000, Extra = Using temporary
튜닝 후 쿼리:
ALTER TABLE users ADD INDEX idx_status_id (status, id);
SELECT o.id, o.amount
FROM orders o
JOIN users u ON o.user_id = u.id
WHERE u.status = 'active';
- 실행 시간: 0.21초
- EXPLAIN 결과: type = ref, rows = 342, Extra = Using where; Using index
단순 인덱스 추가와 SELECT 필드 제한만으로도 20배 이상 성능 향상됨
🧠 실전 조언
- JOIN을 많이 쓴다고 느려지는 게 아니라, 인덱스 없이 JOIN하면 느려지는 것
- 항상 EXPLAIN으로 실행 계획을 확인해야 한다
- MySQL의 JOIN은 옵티마이저에 의존하는 만큼 데이터 분포, 테이블 크기, 인덱스 구성이 모두 성능에 영향을 준다
- 쿼리 캐시 또는 결과 캐싱도 검토해볼 수 있다
✅ 정리
- JOIN은 MySQL에서 가장 많이 쓰이지만 성능 문제를 일으키는 주요 원인이 되기도 한다.
- 조인 조건에는 반드시 인덱스를 걸어야 하며, 불필요한 SELECT 컬럼은 줄이는 것이 좋다.
- 조인 순서와 유형(INNER vs LEFT)에 따라 실행계획이 달라지고 성능도 큰 차이를 만든다.
- 복잡한 조인은 서브쿼리나 CTE로 분해하여 관리 가능성을 높이고 성능도 개선할 수 있다.
🔗 공식 문서 참고
MySQL 8.0 Reference Manual - Join Optimization
'DB' 카테고리의 다른 글
| [MySQL] 복잡한 쿼리 리팩토링: 서브쿼리 → JOIN → CTE 변환 사례 분석 (0) | 2025.07.08 |
|---|---|
| [MySQL] CTE vs 서브쿼리 성능 비교 및 튜닝 포인트 🧠 (0) | 2025.07.08 |
| [MySQL] 서브쿼리 vs JOIN 실전 성능 비교 예제 (0) | 2025.07.08 |
| [MySQL] CTE(Common Table Expression) 개념과 성능 특성 (0) | 2025.07.08 |
| [MySQL] 서브쿼리의 성능 특징과 사용 시 주의점 (0) | 2025.07.08 |
| [MySQL] 서브쿼리 vs JOIN vs CTE 기본 개념 비교 총정리 (2) | 2025.07.08 |
| [MySQL] 슬로우 쿼리 실전 튜닝 사례로 배우는 병목 원인 분석 (1) | 2025.07.07 |
| [MySQL] mysqldumpslow, pt-query-digest로 슬로우 쿼리 분석하기 🔧 (4) | 2025.07.04 |