11/27

졸용·2025년 11월 27일

TIL

목록 보기
123/144

🔹 조인 튜닝 방법에 대해 알아보기

실무에서 실제로 성능 차이를 만드는 조인(Join) 튜닝 방법을 알아보았다.
DBMS 종류와 상관없이 공통적으로 적용되는 원칙 위주로 알아봤다.



🔹 1. 조인 순서 튜닝 (Join Order)

대부분의 DB 옵티마이저는 조인 순서를 자동으로 최적화하지만, 통계 정보가 부정확하거나 힌트를 주면 순서를 바꿀 수 있다.

🔸 원칙

  • 먼저 필터링되는(=선택도가 낮은) 테이블을 먼저 읽기
  • 즉, WHERE 조건의 범위가 좁고, 레코드 수가 적게 나오는 테이블 → 조인의 드라이빙(Driving) 테이블로 잡는 것이 유리
  • 불필요한 row를 초기에 제거해놓으면 이후 Hash Join, Merge Join 비용 감소

🔸 예시

-- A 테이블은 조건으로 100건만 나오고,
-- B 테이블은 전체 100만건에서 조건 없다고 가정
SELECT ...
FROM A
JOIN B ON ...
WHERE A.status = 'ACTIVE';

A를 먼저 읽는 것이 유리하다.



🔹 조인 키에 인덱스를 반드시 고려

조인 시 가장 중요한 원칙은 ON 조건의 양쪽 컬럼 모두가 인덱스 후보인지 확인하는 것이다.

🔸 인덱스가 없으면

  • 해시 조인, Full Scan으로 밀어버림 → 메모리·디스크 IO 급증 → 느려짐

🔸 효과적인 경우

  • Primary Key / Foreign Key 조인
  • 자주 조인되는 칼럼
  • WHERE 조건 + JOIN 조건에 동시에 활용되는 칼럼

🔸 예시

JOIN orders o ON o.user_id = u.id
WHERE u.status = 'ACTIVE'

여기서는 users(status, id) 또는 users(status) + orders(user_id) 형태 인덱스가 유리.



🔹 JOIN 방식 선택 (Nested Loop / Hash Join / Merge Join)

DB는 자신이 판단하여 join 방식을 선택한다. 하지만 패턴에 따라 성능 차이가 크게 난다.

🔸 Nested Loop Join (NLJ)

  • 소량 + 인덱스 존재 시 가장 빠름
  • 드라이빙 테이블 10건, 드리븐 테이블 100만건 → 10 * index lookup → OK

🔸 Hash Join

  • 조인 키 인덱스 없어도 됨
  • 대량 데이터를 조인할 때 유리
  • 메모리 사용 많음

🔸 Merge Join

  • 양쪽 정렬 필요 → 정렬 비용 발생
  • 정렬만 끝나면 매우 빠르게 조인

🔸 튜닝 포인트

  1. 인덱스가 유효하면 Nested Loop 유리
  2. 인덱스 없고 대량이면 Hash Join 유리
  3. 이미 정렬된 경우 Merge Join 강력

옵티마이저가 오판하는 경우 힌트로 선택할 수 있음.



🔹 JOIN 대상 컬럼의 데이터 타입 불일치 금지

타입이 다르면 인덱스를 못 쓰고 Full Scan + 함수 연산이 발생함.

🔸 예시

  • INT vs VARCHAR
  • CHAR(10) vs VARCHAR(10)
  • timestamp vs date

🔸 나쁜 예시

SELECT *
FROM orders o
JOIN users u ON o.user_id = CAST(u.id AS VARCHAR)

→ Index 사용 못함 + Hash join 강제



🔹 불필요한 큰 테이블 조인 제거 (Subquery, EXISTS 활용)

중복 데이터 조인은 매우 큰 성능 손실을 준다.

🔸 EXISTS가 더 빠른 경우

SELECT *
FROM users u
WHERE EXISTS(
  SELECT 1 FROM orders o WHERE o.user_id = u.id
)
  • 불필요한 orders 테이블 데이터 조인 제거
  • 특정 조건 존재 여부만 확인할 때 유리

🔸 JOIN 후 GROUP BY 하는 쿼리 대신 EXISTS 또는 DISTINCT로 대체 가능



🔹 JOIN 전에 WHERE 조건으로 최대한 줄이기

조인을 하기 전에 필터링하는 게 조인 후 필터링보다 5~100배 이상 빠르다.

🔸 나쁜 예

SELECT ...
FROM orders o
JOIN users u ON o.user_id = u.id
WHERE o.created_at >= '2024-01-01';

🔸 좋은 예

SELECT ...
FROM (
    SELECT * 
    FROM orders
    WHERE created_at >= '2024-01-01'
) o
JOIN users u ON o.user_id = u.id;

대량 테이블 orders를 먼저 줄여놓으면 조인 비용 급감.



🔹 OUTER JOIN은 신중하게

LEFT JOIN / RIGHT JOIN은 조건 푸시다운이 안 되는 경우가 있음.

🔸 예시: LEFT JOIN 후 WHERE에서 필터링

SELECT *
FROM a
LEFT JOIN b ON a.id = b.a_id
WHERE b.status = 'ACTIVE';

이는 사실상 INNER JOIN이지만
DB는 먼저 OUTER JOIN을 한 뒤 WHERE에서 필터링 → 성능 저하.


🔸 수정

SELECT *
FROM a
JOIN b ON a.id = b.a_id AND b.status = 'ACTIVE';

→ 조인 조건에서 필터를 넣어야 성능 좋음



🔹 중복 JOIN 제거

정말 많은 튜닝 사례에서 발견되는 문제:

  • 같은 테이블을 2번 조인
  • 서브쿼리에서 다시 조회
  • 불필요한 조인으로 row 폭발

🔸 예시

SELECT ...
FROM users u
JOIN orders o ON ...
JOIN orders o2 ON ...

실제로 o2가 필요 없는 경우가 많다.



🔹 조인용 임시 테이블 또는 MATERIALIZED VIEW 사용

대용량 ETL·통계 쿼리에서 자주 사용하는 방법.

  • 조건 이미 필터링된 임시 테이블 생성
  • 필요한 컬럼만 남김 → 폭 줄이기
  • 조인 대상 테이블을 가볍게 만든 뒤 조인

→ 특히 페타바이트 데이터 웨어하우스에서 거의 필수



🔹 실행 계획(Explain Plan) 반드시 분석

실제 조인 튜닝의 핵심은 실행 계획이다.

🔸 체크할 핵심 지표

  • 어떤 조인 방식? (NLJ / hash / merge)
  • 어떤 순서로 조인?
  • Table Full Scan 있는지?
  • 인덱스 Range Scan vs Index Full Scan?
  • Filtered rows % (selectivity)

실행 계획 없이 조인 튜닝은 거의 불가능하다.



🔹 최종 요약

  1. 조인 조건(ON)의 양쪽 컬럼에 꼭 인덱스 고려
  2. 먼저 필터링되는 테이블을 먼저 읽히도록 구조 조정
  3. OUTER JOIN → 조인 조건으로 필터 조건 이동
  4. 데이터 타입 맞춰서 인덱스 효율 극대화
  5. EXPLAIN PLAN으로 실제 조인 방식 확인 후 최적화
profile
꾸준한 공부만이 답이다

0개의 댓글