NL 조인의 결과 순서

2022. 1. 18.·Oracle/Performance

NL 조인은 기본적으로 아우터의 순서로 결과를 반환하지만, NLJ_BATCHING 방식으로 동작하는 NL 조인은 물리 I/O가 발생할 경우 아우터의 순서로 결과를 반환하지 않을 수 있습니다. 아울러 NL 조인의 이너에서 테이블을 액세스하지 않아도 아우터의 순서로 결과를 반환하지 않을 수 있습니다.

 

테스트를 위해 아래와 같이 테이블과 인덱스를 생성하겠습니다.

-- 1
DROP TABLE t1 PURGE;
DROP TABLE t2 PURGE;

CREATE TABLE t1 (c1, c2) AS SELECT ROWNUM, ROWNUM FROM XMLTABLE ('1 to 10000');
CREATE TABLE t2 (c1, c2) AS SELECT ROWNUM, ROWNUM FROM XMLTABLE ('1 to 10000');

CREATE INDEX t1_x1 ON t1 (c1);
CREATE INDEX t2_x1 ON t2 (c1);

 

아래 쿼리의 NL 조인은 NLJ_BATCHING 방식으로 동작합니다. 버퍼 캐시 flush하여 물리 I/O가 발생하고 이로 인해 결과가 아우터의 순서로 반환되지 않습니다.

-- 2
ALTER SYSTEM FLUSH BUFFER_CACHE;

SELECT *
  FROM (SELECT   /*+ LEADING(A) USE_NL(B) INDEX(A) INDEX(B) */
                 ROWNUM AS rn, a.c1, b.c2
            FROM t1 a, t2 b
           WHERE a.c1 > 0
             AND b.c1 = a.c1)
WHERE rn != c1;
...
116 행이 선택되었습니다.

------------------------------------------------
| Id  | Operation                      | Name  |
------------------------------------------------
|   0 | SELECT STATEMENT               |       |
|*  1 |  VIEW                          |       |
|   2 |   COUNT                        |       |
|   3 |    NESTED LOOPS                |       |
|   4 |     NESTED LOOPS               |       |
|*  5 |      INDEX RANGE SCAN          | T1_X1 |
|*  6 |      INDEX RANGE SCAN          | T2_X1 |
|   7 |     TABLE ACCESS BY INDEX ROWID| T2    |
------------------------------------------------

Outline Data
-------------
      NLJ_BATCHING(@"SEL$2" "B"@"SEL$2")

 

NO_NLJ_BATCHING 힌트를 추가하면 NL 조인이 NLJ_PREFETCH 방식으로 동작하여 결과가 아우터 순서대로 반환됩니다. t2 테이블은 BATCH_TABLE_ACCESS_BY_ROWID 방식으로 조회되지만 NL 조인의 이너에서 로우 단위로 액세스되므로 결과 순서에 영향을 미치지 않습니다.

-- 3
ALTER SYSTEM FLUSH BUFFER_CACHE;

SELECT *
  FROM (SELECT   /*+ LEADING(A) USE_NL(B) INDEX(A) INDEX(B) NO_NLJ_BATCHING(B) */
                 ROWNUM AS rn, a.c1, b.c2
            FROM t1 a, t2 b
           WHERE a.c1 > 0
             AND b.c1 = a.c1)
WHERE rn != c1;

선택된 레코드가 없습니다.

-------------------------------------------------------
| Id  | Operation                             | Name  |
-------------------------------------------------------
|   0 | SELECT STATEMENT                      |       |
|*  1 |  VIEW                                 |       |
|   2 |   COUNT                               |       |
|   3 |    TABLE ACCESS BY INDEX ROWID BATCHED| T2    |
|   4 |     NESTED LOOPS                      |       |
|*  5 |      INDEX RANGE SCAN                 | T1_X1 |
|*  6 |      INDEX RANGE SCAN                 | T2_X1 |
-------------------------------------------------------

Outline Data
-------------
      BATCH_TABLE_ACCESS_BY_ROWID(@"SEL$2" "B"@"SEL$2")
      NLJ_PREFETCH(@"SEL$2" "B"@"SEL$2")

 

아래 쿼리의 NL 조인은 이너에서 테이블을 액세스하지 않습니다. 이로 인해 NO_NLJ_BATCHING 힌트를 사용해도 결과가 아우터 순서대로 반환되지 않습니다.

-- 4
ALTER SYSTEM FLUSH BUFFER_CACHE;

SELECT *
  FROM (SELECT   /*+ LEADING(A) USE_NL(B) INDEX(A) INDEX(B) NO_NLJ_BATCHING(B) */
                 ROWNUM AS rn, a.c1
            FROM t1 a, t2 b
           WHERE a.c1 > 0
             AND b.c1 = a.c1)
WHERE rn != c1;
...
54 행이 선택되었습니다.

-------------------------------------
| Id  | Operation           | Name  |
-------------------------------------
|   0 | SELECT STATEMENT    |       |
|*  1 |  VIEW               |       |
|   2 |   COUNT             |       |
|   3 |    NESTED LOOPS     |       |
|*  4 |     INDEX RANGE SCAN| T1_X1 |
|*  5 |     INDEX RANGE SCAN| T2_X1 |
-------------------------------------

 

아래는 앞선 쿼리의 세션 통계입니다. Batched IO block miss count 값이 정렬되지 않은 로우 수와 유사한 것을 알 수 있습니다.

+---+--------------------------------+----------+------------+
|   |NAME                            |     VALUE|CLASS       |
+---+--------------------------------+----------+------------+
| 2 |Batched IO (bound) vector count |    33.00 |Cache + SQL |
|   |Batched IO block miss count     |   101.00 |Cache + SQL | -- 116
|   |Batched IO same unit count      |    67.00 |Cache + SQL |
|   |Batched IO single block count   |    32.00 |Cache + SQL |
|   |Batched IO slow jump count      |    18.00 |Cache + SQL |
|   |Batched IO vector block count   |     2.00 |Cache + SQL |
|   |Batched IO vector read count    |     1.00 |Cache + SQL |
+---+--------------------------------+----------+------------+
| 3 |Batched IO (bound) vector count |    16.00 |Cache + SQL |
|   |Batched IO single block count   |    16.00 |Cache + SQL |
+---+--------------------------------+----------+------------+
| 4 |Batched IO (bound) vector count |    18.00 |Cache + SQL |
|   |Batched IO block miss count     |    54.00 |Cache + SQL | -- 54
|   |Batched IO same unit count      |    36.00 |Cache + SQL |
|   |Batched IO single block count   |    18.00 |Cache + SQL |
+---+--------------------------------+----------+------------+

 

NLJ_BATCHING 방식은 아래 파라미터와 관련이 있습니다.

NAME                         VALUE DEFAULT_VALUE DESCRIPTION
---------------------------- ----- ------------- -------------------------------------------------------
_nlj_batching_ae_flag        2     2             FAE flag type set after restoring to IO batching buffer
_nlj_batching_enabled        1     0             enable batching of the RHS IO in NLJ
_nlj_batching_misses_enabled 1     1             enable exceptions for buffer cache misses

 

_nlj_batching_misses_enabled 파라미터를 0으로 설정하고 쿼리를 수행하면 결과가 아우터 순서대로 반환됩니다. 참고로 _nlj_batching_enabled 파라미터를 0으로 설정하면 결과가 아우터 순서대로 반환되지 않습니다.

-- 6
ALTER SYSTEM FLUSH BUFFER_CACHE;

ALTER SESSION SET "_nlj_batching_misses_enabled" = 0;

SELECT *
  FROM (SELECT   /*+ LEADING(A) USE_NL(B) INDEX(A) INDEX(B) */
                 ROWNUM AS rn, a.c1
            FROM t1 a, t2 b
           WHERE a.c1 > 0
             AND b.c1 = a.c1)
WHERE rn != c1;

선택된 레코드가 없습니다.

-------------------------------------
| Id  | Operation           | Name  |
-------------------------------------
|   0 | SELECT STATEMENT    |       |
|*  1 |  VIEW               |       |
|   2 |   COUNT             |       |
|   3 |    NESTED LOOPS     |       |
|*  4 |     INDEX RANGE SCAN| T1_X1 |
|*  5 |     INDEX RANGE SCAN| T2_X1 |
-------------------------------------
저작자표시 비영리 변경금지 (새창열림)
'Oracle/Performance' 카테고리의 다른 글
  • 병렬 MERGE 문의 데이터 분배
  • 자동 통계 수집과 STALE_PERCENT
  • 조인 칼럼의 low_value와 high_value에 의한 조인 카디널리티
  • low_value와 high_value를 벗어난 값에 대한 예상 카디널리티
정희락
정희락
2007년부터 Oracle Database 성능 최적화에 관심을 가져왔습니다. 현재 한국오라클 Engineered Systems Solution Engineering 팀에서 Solution Engineer로 근무하고 있습니다. 이 블로그는 개인적인 연구 목적으로 운영되며 Oracle 사의 공식적인 입장을 대변하지 않습니다.
  • 정희락
    TunA
    정희락
  • 전체
    오늘
    어제
    • 분류 전체보기 (201)
      • Oracle (173)
        • SQL (33)
        • PLSQL (10)
        • Performance (74)
        • Administration (37)
        • Installation (3)
        • Utilities (1)
        • JSON (8)
        • Vector (7)
      • Exadata (15)
      • SQL*Plus (2)
      • Linux (5)
      • Resources (6)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 도서

    • 불친절한 SQL 프로그래밍
    • 불친절한 PL/SQL 프로그래밍
  • 링크

    • Connor McDonald
    • Frits Hoogland
    • Jonathan Lewis
    • Julian Dontcheff
    • Julian Dyke
    • Kun Sun
    • Maria Colgan
    • Martin Bach
    • Mike Dietrich
    • Tanel Poder
  • 공지사항

  • 인기 글

  • 태그

    12c
    19c
    21c
    23ai
    case study
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
정희락
NL 조인의 결과 순서
상단으로

티스토리툴바