DBDBDEEP
4.1 NL 조인 본문
Nested Loop Join 티베로와 오라클의 차이
오라클에서는 N*M Join을 수행할 때 Catesian Product (카테시안 곱) 으로 정의한다.
티베로에서는 N*M Join을 수행할 때 Nested Loop Join으로 정의한다.
후행테이블에 Index가 존재할 때, 오라클에서는 이 방식을 Nested Loop Join 으로 정의한다.
후행테이블에 Index가 존재할 때, 티베로에서는 이 방식을 Index Join으로 정의한다.
티베로의 경우 실행계획에 Nested Loop(Oracle Catesian)로 나오게 된다면 해당 케이스는 '카테시안 곱'인 것이고
해당 실행계획이 Index Join(Oracle Nested Loop)을 탈 수 있게 바꾸면 티베로의 경우 거의 무조건 빠를 수 있다 (쉬운 튜닝 케이스)
티베로에서는 index join을 타게 하려면
leading + use_nl (후행테이블) + use_nl_with_index(후행테이블 + 인덱스)같이 힌트를 명확히 줘서 할 수 있게 해야한다.
아래 케이스는 Index Join을 탈 수 있도록 use_nl_with_index 힌트를 주고 있다.
힌트를 추가하지 않으면 nested Loop로 실행계획이 생성되어 부분범위 처리가 되지 않게 된다.
| select * from ( select /*+ leading(t1) use_nl(t2) index_desc(t1) */ t1.c2 , t1.c3 , t2.c1 from tb1 t1 left outer join tb2 t2 on t1.c1 = t2.c1 and t2.c1 = :v_c1 where 1=1 and t1.c3 between :v_c3_s and :v_c3_e order by t1.c2 desc, t1.c3 desc ) where rownum <= :PageNo * :RecordCount ; Execution Stat 1 ORDER BY (SORT) TOP-N (Time:2.33 ms, Rows:10, Starts:1) 2 NESTED LOOPS (LEFT OUTER) (Time:2.02 ms, Rows:6670, Starts:1) 3 TABLE ACCESS (ROWID): TB1 (Time:3.96 ms, Rows:6670, Starts:1) 4 INDEX (SKIP SCAN) DESCENDING: TB1_IDX (Time:2.04 ms, Rows:6670, Starts:1) 5 INDEX (RANGE SCAN): TB2_IDX (Time:.04 ms, Rows:1, Starts:1) Predicate Information 2 - access: ("T1"."C1" = "T2"."C1") (0.000) 4 - access: ("T1"."C3" >= :V_C3_S) AND ("T1"."C3" <= :V_C3_E) (1.000 * 0.774) 5 - access: ("T2"."C1" = :V_C1) (1.000) select * from ( select /*+ leading(t1) use_nl(t2) index_desc(t1) use_nl_with_index(t2 tb2_idx) */ t1.c2 , t1.c3 , t2.c1 from tb1 t1 left outer join tb2 t2 on t1.c1 = t2.c1 and t2.c1 = :v_c1 where 1=1 and t1.c3 between :v_c3_s and :v_c3_e order by t1.c2 desc, t1.c3 desc ) where rownum <= :PageNo * :RecordCount ; Execution Stat 1 COUNT (STOP NODE) (Time:.21 ms, Rows:10, Starts:1) 2 INDEX JOIN (LEFT OUTER) (Time:.02 ms, Rows:10, Starts:1 3 TABLE ACCESS (ROWID): TB1 (Time:.03 ms, Rows:10, Starts:1) 4 INDEX (SKIP SCAN) DESCENDING: TB1_IDX (Time:.18 ms, Rows:115, Starts:1) 5 FILTER (Time:.01 ms, Rows:0, Starts:10) 6 INDEX (RANGE SCAN): TB2_IDX (Time:.21 ms, Rows:10, Starts:10) |
부분 범위 처리 시 Tibero에서는
메모리에 선행테이블을 올리고 메모리가 꽉 차게 되어서 디스크에서 메모리를 올리게 되는 케이스가 오면 정렬이안되는 경우가 있는 것으로 보인다.
NL 조인 실행계획과 쿼리
| select /*+ ordered use_nl (c) */ |
| e.사원명, c.고객명, c.전화번호 from 사원 e, 고객 c where e.입사일자 >= '19960101' and c.관리사원번호 = e.사원번호 *TIP select /*+ use_nl(A,B,C,D) */* from A,B,C,D where … 이렇게 되어 있다면 내게 테이블을 NL 방식으로 조인하되 순서는 옵티마이저가 스스로 정하도록 맡김 |
위 경우에는 사원 테이블 (Driving 또는 Outer Table) 기준으로
고객 테이블 (Inner 테이블)과 NL 조인 방식으로 조인하라는 의미이다.
실행계획을 확인할 때에는
위의 '사원 테이블을 기준으로 고객테이블과 NL 조인 한다고 이해'하면 되며 각 테이블을 액세스 할 때 인덱스를 사용한다는 것도 실행계획에서 확인할 수 있다.
아래 그림에서 제일 중요한 것은 각 단계를 모두 완료하고 다음 단계로 넘어가는 것이 아닌,
한 레코드씩 순차적으로 진행한다는 사실이다.
아래 단계에서 11,19,31,32는 스캔할 데이터가 더 있는지 확인하는 One-Plus 스캔을 표시한 것이다.
O는 테이블 필터 조건에 의해 걸러지지 않은 것 / X는 테이블 필터 조건에 의해 걸러진 것.

NL 조인 특징 요약
1. 랜덤액세스 위주 의 조인 방식이라는 점. 레코드 하나를 읽으려고 블록을 통째로 읽는 랜덤 액세스 방식은
설령 메모리 버퍼에서 빠르게 읽더라도 비효율이 존재하게 된다.
2. 조인을 한 레코드를 순차적으로 진행한다는 점.
첫 번째 특징에서 대량 데이터 처리시 매우 치명적인 한계를 드러내는데, 두 번째 특징으로 인해서 아무리 큰 테이블을 조인하더라도 매우 빠른 응답속도를 낼 수 있음 (부분 범위 처리 상황)
부분범위 처리를 사용한다면 사용자가 조회 버튼을 누르자마자 결과 집합을 출력하기 시작한다.
3. 순차적으로 진행하므로 먼저 액세스되는 테이블 처리 범위에 의해 전체 일량이 결정되는 특징이 나타난다.
4. 다른 조인 방식과 비교할 때 인덱스 구성 전략이 특히 중요
조인 컬럼에 대한 인덱스가 있느냐 없느냐에 따라 조인 효율이 크게 달라진다.
NL조인은 소량 데이터를 주로 처리하거나 / 부분범위 처리가 가능한 OLTP 시스템에 적합한 조인 방식이다.
'친절한 SQL 튜닝' 카테고리의 다른 글
| 4.3 해시조인 (0) | 2026.05.18 |
|---|---|
| 4.2 소트 머지 조인 (0) | 2026.05.18 |
| LIKE/BETWEEN 조건 활용 (0) | 2026.05.18 |
| IN 조건이 '='으로 해석될 수 있는지 (0) | 2026.05.18 |
| 3.3.5 인덱스 선행 컬럼이 등치(=) 조건이 아닐 때 (0) | 2026.05.18 |