홈 › 대용량·배치 › 02 / 8

조인 방식: Nested Loop, Hash, Sort Merge 와 드라이빙 테이블

섹션 6진행 0 / 8

4. 응용 변형 예제

아래 도식은 이 레슨의 데이터 모양을 줄여 손으로 그린 설명용입니다. 실행 결과가 아니고 DB가 실제로 이렇게 출력한다는 뜻도 아닙니다.

예제 5: Nested Loop 단계 도식

dept 에서 개발팀 1행을 읽고 emp 의 dept 인덱스로 짝을 찾는 흐름입니다.

도식(H2 미지원, 실행 결과 아님)

text
드라이빙: dept (조건 dname = '개발팀')
 1) dept 에서 D1 을 읽는다              -> 바깥 1행
 2) emp 인덱스에서 dept = 'D1' 을 찾는다 -> 60행 짝
 3) 짝마다 결과를 낸다                   -> 첫 행이 바로 나감
 4) dept 다음 행이 없으면 종료

바깥이 1행이라 안쪽 탐색은 1번입니다. 반대로 emp 를 바깥으로 삼으면 emp 300행마다 dept 를 찾아야 합니다.

text
드라이빙: emp (300행)
 1) emp 1행을 읽는다
 2) dept 기본 키로 그 행의 dept 를 찾는다
 3) dname = '개발팀' 이 아니면 버린다
 4) 300번 반복

옵티마이저가 통계로 앞의 쪽을 고르는 것이 보통입니다. 사람이 FROM 순서로 지시하는 것이 아닙니다.

예제 6: Hash 단계 도식

작은 dept 를 빌드하고 큰 emp 를 프로브합니다. 아래는 dept 3행, emp 4행으로 줄여 그렸습니다.

도식(H2 미지원, 실행 결과 아님)

text
1) 빌드: dept 를 읽어 메모리 해시 표를 만든다
   해시 표
   D1 -> 개발팀
   D2 -> 영업팀
   D3 -> 인사팀

2) 프로브: emp 를 한 행씩 읽고 dept 값으로 해시 표를 찾는다
   emp1(D2) -> 영업팀  맞음
   emp2(D1) -> 개발팀  맞음
   emp3(D3) -> 인사팀  맞음
   emp4(D9) -> 없음    버림

dept 와 emp 를 각각 한 번씩만 읽습니다. emp 에 인덱스가 없어도 됩니다. 해시 표가 메모리를 넘으면 임시 공간을 써서 느려지므로, 더 작은 표를 빌드 쪽으로 고르는 것이 중요합니다.

예제 7: Sort Merge 단계 도식

양쪽을 dept 코드로 정렬한 뒤 두 줄을 함께 내려 읽습니다.

도식(H2 미지원, 실행 결과 아님)

text
1) 정렬       dept: D1 D2 D3      emp: D1 D1 D2 D3 D3
2) 병합       D1 = D1  짝, 짝(emp 두 행)
              D2 = D2  짝
              D3 = D3  짝, 짝(emp 두 행)
3) 각 표를 한 번씩만 훑고 끝난다

범위 조건이면 병합 중 한쪽 줄을 되돌려 겹치는 구간을 다시 봅니다. 이미 정렬된 입력이 많거나 결과를 정렬해서 쓸 때 정렬 비용이 줄어듭니다.

예제 8: DB별 계획 모양과 힌트

같은 dept ↔ emp 조인이 각 DB에서 어떤 모양으로 보이는지 줄여 그렸습니다. 실제 출력은 버전과 통계로 달라집니다.

도식(H2 미지원, 실행 결과 아님)

text
-- Oracle · Tibero
HASH JOIN
  TABLE ACCESS FULL   DEPT
  TABLE ACCESS FULL   EMP

-- MySQL (8.0.18 이상)
-> Inner hash join (e.dept = d.code)
    -> Table scan on e
    -> Hash
        -> Table scan on d

-- MSSQL
Hash Match (Inner Join)
  Clustered Index Scan   dept
  Clustered Index Scan   emp

방식을 지정할 때는 DB별 힌트를 씁니다. Oracle 은 LEADING 으로 드라이빙 순서를, USE_NL·USE_HASH·USE_MERGE 로 방식을 정합니다. MSSQL 은 쿼리 끝에 OPTION 으로 지정합니다.

sql
-- Oracle · Tibero
SELECT /*+ LEADING(d) USE_NL(e) */
       e.name
  FROM dept d
  JOIN emp e ON e.dept = d.code
 WHERE d.dname = '개발팀';
sql
-- MSSQL
SELECT
       e.name
  FROM dept d
  JOIN emp e ON e.dept = d.code
 WHERE d.dname = '개발팀'
OPTION (HASH JOIN);

힌트는 옵티마이저 선택을 고정하므로 데이터가 늘거나 통계가 바뀌면 오히려 느려질 수 있습니다. 쓰는 기준과 위험은 대용량 04 레슨에서 다룹니다.