Notice
Recent Posts
Recent Comments
Link
«   2026/06   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
Archives
Today
Total
관리 메뉴

DBDBDEEP

4.1 NL 조인 본문

친절한 SQL 튜닝

4.1 NL 조인

Kihwane 2026. 5. 18. 10:56

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 시스템에 적합한 조인 방식이다.