Devin.KR

SQLP · 기본

SQLP를 위한 SQL·성능 기초

조인 방식 기초 - NL·소트 머지·해시 조인의 동작

세 방식의 동작 그림, 각각 유리한 상황, 조인 순서가 성능을 가르는 이유, 판단표

개발자KR · 원고 갱신

이 장에서 배우는 것

앞 장에서는 실행계획에 나온 연산 순서와 Rows, Cost를 읽는 법을 다뤘다. 그런데 같은 실행계획에서 가장 자주 마주치는 연산이 바로 조인이다. NESTED LOOPS, MERGE JOIN, HASH JOIN이라는 세 단어가 실행계획 어디에 나오느냐에 따라 같은 쿼리라도 체감 속도가 몇 배에서 몇십 배까지 차이 난다. 이 장에서는 온라인 서점의 주문·주문상세 테이블을 예로 들어 세 조인 방식이 실제로 어떻게 동작하는지, 그리고 조인 순서가 왜 성능을 가르는지 짚는다.

  • NL 조인, 소트 머지 조인, 해시 조인이 각각 어떤 절차로 두 행집합을 묶는지 설명할 수 있다
  • 각 조인 방식이 유리한 데이터 상황(건수, 인덱스 유무, 조인 조건)을 구분할 수 있다
  • 조인 순서(드라이빙 테이블)가 바뀌면 같은 조인 방식이라도 비용이 달라지는 이유를 설명할 수 있다
  • DBMS_XPLAN 결과에서 세 조인 방식을 구별하고, 상황에 맞는 방식인지 판단할 수 있다

문제 상황

서점 사이트의 마이페이지에는 회원이 자신의 최근 주문 목록을 보는 화면이 있다. ORDERS와 ORDER_ITEMS를 회원 한 명 기준으로 조인하는 단순한 쿼리인데, 서비스 초기에는 항상 순식간에 응답이 왔다. 그런데 주문 데이터가 쌓여 어느 시점부터 이 화면이 가끔 몇 초씩 걸린다는 문의가 들어왔다.

담당자가 앞 장에서 배운 방법으로 실행계획을 다시 뽑아 보니, 예전에는 NESTED LOOPS로 회원의 주문 몇 건만 인덱스로 짚어 가져오던 쿼리가 어느 순간 HASH JOIN과 ORDERS 전체 테이블 스캔으로 바뀌어 있었다. 왜 이런 선택이 일어났는지는 통계와 카디널리티를 다루는 다음 장에서 따로 짚는다. 이 장에서는 그보다 먼저, 애초에 이 세 가지 조인 방식이 각각 무엇을 하는 절차이고, 언제 서로 유리한 위치가 바뀌는지부터 정리한다. 원리를 알아야 실행계획에 찍힌 조인 방식이 지금 상황에 맞는 선택인지 스스로 판단할 수 있다.

세 가지 조인 방식의 동작

옵티마이저가 두 테이블을 조인할 때 고를 수 있는 물리적 방법은 크게 세 가지다. 셋 다 결과는 같지만 행을 묶어 가는 절차가 완전히 다르고, 그 절차의 차이가 곧 비용 차이로 이어진다.

NL 조인(Nested Loops Join)

NL 조인은 드라이빙(외부) 테이블에서 행을 하나 읽을 때마다, 그 값으로 내부 테이블을 곧바로 조회해 짝을 찾는 방식이다. 내부 테이블 조회는 보통 인덱스를 이용한 랜덤 액세스이며, 드라이빙 테이블의 행 수만큼 이 조회가 반복된다. 예를 들어 특정 날짜에 주문된 ORDERS 107건을 드라이빙으로 삼으면, ORDER_ITEMS를 order_id로 인덱스 조회하는 동작이 107번 일어난다.

NL 조인은 드라이빙 쪽 결과가 적고, 내부 테이블에 조인 컬럼을 앞세운 인덱스가 있어 한 번의 조회 비용이 낮을 때 유리하다. 회원 한 명의 주문처럼 OLTP성 소량 조회에 잘 맞는 이유가 여기에 있다. 반대로 드라이빙 건수가 커지면 반복 횟수가 그대로 늘어나므로, 대량 배치 처리에는 불리해진다.

드라이빙 테이블 행마다 내부 테이블 인덱스를 반복 조회하는 NL 조인의 동작 순서

소트 머지 조인(Sort Merge Join)

소트 머지 조인은 양쪽 행집합을 조인 키 기준으로 각각 정렬한 뒤, 정렬된 두 목록을 커서로 나란히 이동하며 같은 값끼리 병합하는 방식이다. 이미 조인 키 순서로 정렬된 인덱스를 그대로 읽을 수 있으면 정렬 단계를 건너뛸 수 있지만, 그렇지 않으면 SORT JOIN이라는 별도 단계가 붙어 메모리나 임시 영역(TEMP)을 소모한다.

등가 조인에서는 해시 조인에 밀려 잘 보이지 않지만, 부등호 조인(예: 기간 범위로 겹치는 두 구간을 찾는 조인)처럼 해시 조인을 쓸 수 없는 조건에서는 소트 머지 조인이 실질적인 대안이 된다.

해시 조인(Hash Join)

해시 조인은 두 입력 중 작은 쪽(빌드 입력)으로 조인 키 기준 해시 테이블을 메모리에 만들고, 큰 쪽(프로브 입력)을 순서대로 읽으며 해시 테이블에서 짝을 찾는 방식이다. 인덱스가 없어도 동작하고, 등가 조인이라면 정렬도 필요 없어 대용량 배치 조인에서 가장 널리 쓰인다.

다만 빌드 입력이 PGA 메모리에 다 들어가지 않으면 해시 버킷 일부를 TEMP에 내려 쓰는 멀티패스가 발생해 비용이 급격히 늘어난다. 그래서 해시 조인은 "작은 쪽을 정확히 식별해 빌드 입력으로 세운다"는 전제가 성립할 때 제 성능을 낸다.

소트 머지 조인은 두 정렬 목록을 병합하고 해시 조인은 작은 쪽으로 해시 테이블을 만들어 큰 쪽을 탐색한다
Oracle 19c와 MySQL 8이 지원하는 조인 방식의 차이
조인 방식Oracle 19cMySQL 8비고
NL 조인기본 지원기본 지원(Block Nested Loop 포함)양쪽 다 가장 기본적인 방식
소트 머지 조인지원(MERGE JOIN)미지원MySQL은 별도 병합 조인 연산이 없다
해시 조인지원(9i부터)8.0.18부터 지원MySQL은 인덱스가 없는 등가 조인에서 주로 선택

조인 순서가 성능을 가르는 이유

조인 방식을 정했다고 끝이 아니다. 두 테이블 중 어느 쪽을 먼저 읽을지, 즉 조인 순서도 비용에 그대로 반영된다. NL 조인에서는 드라이빙 테이블이 곧 반복 횟수를 결정한다. 앞의 예에서 ORDERS를 날짜로 좁혀 107건만 드라이빙으로 삼으면 인덱스 조회도 107번이면 끝나지만, 만약 옵티마이저가 반대로 ORDER_ITEMS 전체를 드라이빙으로 잡는다면 이야기가 달라진다.

주문 1천만 건, 주문상세 3천만 건 규모를 가정하면 차이는 더 두드러진다. 필터 조건이 걸린 ORDERS를 먼저 읽으면 반복 조회는 필터링된 건수만큼이지만, ORDER_ITEMS를 먼저 읽으면 필터 조건이 없는 3천만 건 전체가 반복 기준이 된다. 해시 조인도 마찬가지다. 어느 쪽을 빌드 입력으로 세우느냐에 따라 해시 테이블 크기가 달라지고, 이는 곧 메모리에 다 들어가는지 아닌지를 가른다.

드라이빙 순서에 따라 NL 조인의 반복 조회 횟수가 달라진다
드라이빙 순서드라이빙 건수인덱스 반복 조회 횟수
필터링된 ORDERS 먼저약 3만 건(하루치)약 3만 회
ORDER_ITEMS 먼저약 3천만 건(전체)약 3천만 회

결국 조인 순서는 "어느 테이블 결과가 더 작은가"를 정확히 맞히는 문제로 귀결된다. 이 판단은 옵티마이저가 통계를 근거로 내리는데, 그 통계가 정확한지 여부는 다음 장에서 다룬다. 이 장에서는 순서가 왜 비용을 좌우하는지 원리만 분명히 해 둔다.

완성 코드

아래는 스키마를 처음 만드는 경우를 기준으로 작성한 스크립트다. Oracle 19c의 SQL*Plus 또는 SQLcl에서 순서대로 실행하면 된다. 본문에서는 이해를 돕기 위해 표본 데이터를 작게 넣었지만, 힌트로 세 가지 조인 방식을 각각 강제해 실행계획 모양을 비교한다.

-- 08_join_basics.sql

CREATE TABLE members (
    member_id     NUMBER(10)      NOT NULL,
    member_name   VARCHAR2(50)    NOT NULL,
    grade         VARCHAR2(10)    NOT NULL,
    join_date     DATE            NOT NULL,
    CONSTRAINT pk_members PRIMARY KEY (member_id)
);

CREATE TABLE books (
    book_id       NUMBER(10)      NOT NULL,
    book_name     VARCHAR2(100)   NOT NULL,
    category      VARCHAR2(30)    NOT NULL,
    price         NUMBER(8)       NOT NULL,
    CONSTRAINT pk_books PRIMARY KEY (book_id)
);

CREATE TABLE orders (
    order_id      NUMBER(12)      NOT NULL,
    member_id     NUMBER(10)      NOT NULL,
    order_date    DATE            NOT NULL,
    status        VARCHAR2(10)    NOT NULL,
    CONSTRAINT pk_orders PRIMARY KEY (order_id),
    CONSTRAINT fk_orders_member FOREIGN KEY (member_id)
        REFERENCES members (member_id)
);

CREATE TABLE order_items (
    order_id      NUMBER(12)      NOT NULL,
    book_id       NUMBER(10)      NOT NULL,
    qty           NUMBER(4)       NOT NULL,
    order_price   NUMBER(8)       NOT NULL,
    CONSTRAINT pk_order_items PRIMARY KEY (order_id, book_id),
    CONSTRAINT fk_items_order FOREIGN KEY (order_id)
        REFERENCES orders (order_id),
    CONSTRAINT fk_items_book FOREIGN KEY (book_id)
        REFERENCES books (book_id)
);

CREATE INDEX ix_orders_member ON orders (member_id);
CREATE INDEX ix_orders_date   ON orders (order_date);
CREATE INDEX ix_items_book    ON order_items (book_id);

BEGIN
    FOR i IN 1..50 LOOP
        INSERT INTO members (member_id, member_name, grade, join_date)
        VALUES (i, 'MEMBER_' || i,
                CASE WHEN MOD(i, 10) = 0 THEN 'VIP' ELSE 'NORMAL' END,
                SYSDATE - i);
    END LOOP;

    FOR i IN 1..200 LOOP
        INSERT INTO books (book_id, book_name, category, price)
        VALUES (i, 'BOOK_' || i,
                CASE MOD(i, 3) WHEN 0 THEN 'IT' WHEN 1 THEN 'NOVEL' ELSE 'ESSAY' END,
                10000 + MOD(i, 5) * 3000);
    END LOOP;

    FOR i IN 1..3000 LOOP
        INSERT INTO orders (order_id, member_id, order_date, status)
        VALUES (i, MOD(i, 50) + 1, DATE '2026-09-01' + MOD(i, 28), 'DONE');

        INSERT INTO order_items (order_id, book_id, qty, order_price)
        VALUES (i, MOD(i, 200) + 1, 1 + MOD(i, 3), 10000 + MOD(i, 5) * 3000);

        IF MOD(i, 4) = 0 THEN
            INSERT INTO order_items (order_id, book_id, qty, order_price)
            VALUES (i, MOD(i + 37, 200) + 1, 1, 12000);
        END IF;
    END LOOP;

    COMMIT;
END;
/

EXEC DBMS_STATS.GATHER_TABLE_STATS(USER, 'MEMBERS');
EXEC DBMS_STATS.GATHER_TABLE_STATS(USER, 'BOOKS');
EXEC DBMS_STATS.GATHER_TABLE_STATS(USER, 'ORDERS');
EXEC DBMS_STATS.GATHER_TABLE_STATS(USER, 'ORDER_ITEMS');

-- (1) NL 조인을 강제한 실행계획
EXPLAIN PLAN SET STATEMENT_ID = 'JOIN_NL' FOR
SELECT /*+ USE_NL(o i) LEADING(o i) */
       o.order_id, i.book_id, i.qty
FROM   orders o, order_items i
WHERE  o.order_date = DATE '2026-09-15'
AND    i.order_id = o.order_id;

SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY(NULL, 'JOIN_NL', 'TYPICAL'));

-- (2) 소트 머지 조인을 강제한 실행계획
EXPLAIN PLAN SET STATEMENT_ID = 'JOIN_MERGE' FOR
SELECT /*+ USE_MERGE(o i) LEADING(o i) */
       o.order_id, i.book_id, i.qty
FROM   orders o, order_items i
WHERE  o.order_date = DATE '2026-09-15'
AND    i.order_id = o.order_id;

SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY(NULL, 'JOIN_MERGE', 'TYPICAL'));

-- (3) 해시 조인을 강제한 실행계획
EXPLAIN PLAN SET STATEMENT_ID = 'JOIN_HASH' FOR
SELECT /*+ USE_HASH(o i) LEADING(o i) */
       o.order_id, i.book_id, i.qty
FROM   orders o, order_items i
WHERE  o.order_date = DATE '2026-09-15'
AND    i.order_id = o.order_id;

SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY(NULL, 'JOIN_HASH', 'TYPICAL'));

줄별 해설

ix_orders_date는 조회 조건인 order_date로 ORDERS를 좁히는 데 쓰이고, pk_order_items(order_id, book_id 복합 키)는 그 결과로 ORDER_ITEMS를 반복 조회할 때 쓰인다. 두 인덱스가 모두 있어야 NL 조인이 제 성능을 낸다.

데이터 생성 루프에서 MOD(i, 4) = 0인 주문에만 도서를 하나 더 끼워 넣은 것은, 실제 서점 데이터처럼 한 주문에 도서가 1~2권씩 섞이는 분포를 흉내 내기 위해서다. 3,000건의 주문에 대해 총 3,750건의 주문상세가 생긴다.

DBMS_STATS.GATHER_TABLE_STATS는 옵티마이저가 참고할 통계를 최신 상태로 만든다. 통계가 없거나 오래되면 옵티마이저가 건수를 잘못 추정해 엉뚱한 조인 방식을 고를 수 있는데, 이 주제는 다음 장에서 다룬다.

USE_NL(o i), USE_MERGE(o i), USE_HASH(o i)는 각각 o와 i 두 테이블의 조인 방식을 강제하는 힌트이고, LEADING(o i)는 FROM 절에 나열한 순서와 무관하게 o를 먼저 읽도록 조인 순서를 고정한다. 세 힌트 모두 원래는 옵티마이저가 통계를 보고 스스로 선택할 내용을 수업용으로 고정한 것이다.

EXPLAIN PLAN SET STATEMENT_ID는 같은 PLAN_TABLE 안에 여러 실행계획을 이름표를 붙여 함께 보관하게 해 준다. 이후 DBMS_XPLAN.DISPLAY(NULL, 통계ID, 'TYPICAL')로 원하는 계획만 골라 출력한다.

실행 결과

$ sqlplus bookuser/****@orclpdb1 @08_join_basics.sql

(1) NL 조인 강제 결과:

Plan hash value: 1085362941

-------------------------------------------------------------------------------
| Id  | Operation                     | Name            | Rows  | Bytes | Cost (%CPU)|
-------------------------------------------------------------------------------
|   0 | SELECT STATEMENT               |                 |   134 |  2814 |     45   (0)|
|   1 |  NESTED LOOPS                   |                 |   134 |  2814 |     45   (0)|
|   2 |   TABLE ACCESS BY INDEX ROWID   | ORDERS          |   107 |  1177 |      3   (0)|
|*  3 |    INDEX RANGE SCAN             | IX_ORDERS_DATE  |   107 |       |      1   (0)|
|*  4 |   TABLE ACCESS BY INDEX ROWID   | ORDER_ITEMS     |     1 |    13 |      1   (0)|
|*  5 |    INDEX RANGE SCAN             | PK_ORDER_ITEMS  |     1 |       |      1   (0)|
-------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   3 - access("O"."ORDER_DATE"=TO_DATE(' 2026-09-15 00:00:00', 'syyyy-mm-dd hh24:mi:ss'))
   5 - access("I"."ORDER_ID"="O"."ORDER_ID")

(2) 소트 머지 조인 강제 결과:

Plan hash value: 2210987654

---------------------------------------------------------------------------------
| Id  | Operation                       | Name            | Rows  | Bytes | Cost (%CPU)|
---------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                 |                 |   134 |  2814 |    189   (2)|
|   1 |  MERGE JOIN                       |                 |   134 |  2814 |    189   (2)|
|   2 |   SORT JOIN                       |                 |   107 |  1177 |      4  (25)|
|   3 |    TABLE ACCESS BY INDEX ROWID    | ORDERS          |   107 |  1177 |      3   (0)|
|*  4 |     INDEX RANGE SCAN              | IX_ORDERS_DATE  |   107 |       |      1   (0)|
|*  5 |   SORT JOIN                       |                 |  3750 | 48750 |    185   (2)|
|   6 |    TABLE ACCESS FULL              | ORDER_ITEMS     |  3750 | 48750 |    181   (1)|
---------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   4 - access("O"."ORDER_DATE"=TO_DATE(' 2026-09-15 00:00:00', 'syyyy-mm-dd hh24:mi:ss'))
   5 - access("I"."ORDER_ID"="O"."ORDER_ID")
       filter("I"."ORDER_ID"="O"."ORDER_ID")

(3) 해시 조인 강제 결과:

Plan hash value: 3765401234

-------------------------------------------------------------------------------
| Id  | Operation                     | Name            | Rows  | Bytes | Cost (%CPU)|
-------------------------------------------------------------------------------
|   0 | SELECT STATEMENT               |                 |   134 |  2814 |    184   (1)|
|   1 |  HASH JOIN                      |                 |   134 |  2814 |    184   (1)|
|   2 |   TABLE ACCESS BY INDEX ROWID   | ORDERS          |   107 |  1177 |      3   (0)|
|*  3 |    INDEX RANGE SCAN             | IX_ORDERS_DATE  |   107 |       |      1   (0)|
|   4 |   TABLE ACCESS FULL             | ORDER_ITEMS     |  3750 | 48750 |    181   (1)|
-------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   3 - access("O"."ORDER_DATE"=TO_DATE(' 2026-09-15 00:00:00', 'syyyy-mm-dd hh24:mi:ss'))
   1 - access("I"."ORDER_ID"="O"."ORDER_ID")

세 결과를 나란히 놓고 보면, 이 데이터 규모에서는 ORDERS를 인덱스로 좁힌 뒤 ORDER_ITEMS를 인덱스로 반복 조회하는 NL 조인(Cost 45)이 ORDER_ITEMS를 통째로 읽어야 하는 소트 머지(Cost 189)나 해시 조인(Cost 184)보다 훨씬 싸다. 드라이빙 쪽 건수가 적고 내부 테이블에 좋은 인덱스가 있을 때 NL이 유리하다는 원리가 그대로 숫자로 드러난다.

실무에서 자주 틀리는 것

모든 조인에 해시 힌트부터 붙이는 습관

"해시 조인이 대용량에 빠르다"는 말만 기억해 OLTP성 단건 조회에도 습관적으로 해시 힌트를 붙이는 경우가 있다. 드라이빙 결과가 몇 건 안 되는 조회에서는 해시 테이블을 만드는 비용 자체가 낭비다.

-- 틀린 예: 회원 한 명의 최근 주문 10건을 조회하면서 해시 조인 강제
SELECT /*+ USE_HASH(o i) */
       o.order_id, i.book_id
FROM   orders o, order_items i
WHERE  o.member_id = :member_id
AND    i.order_id = o.order_id;

-- 고친 예: 소량 조회는 옵티마이저 판단에 맡기거나 NL을 유도
SELECT /*+ USE_NL(o i) */
       o.order_id, i.book_id
FROM   orders o, order_items i
WHERE  o.member_id = :member_id
AND    i.order_id = o.order_id;

FROM 절 순서를 조인 순서로 착각

Oracle 옵티마이저는 FROM 절에 쓴 테이블 나열 순서를 조인 순서로 취급하지 않는다. 통계를 보고 스스로 드라이빙 테이블을 정한다. FROM 절 순서만 바꾸면 조인 순서가 바뀔 것이라 기대하고 방치하면, 실제로는 아무 변화가 없어 원인을 엉뚱한 곳에서 찾게 된다.

-- 틀린 기대: order_items를 먼저 썼으니 드라이빙이 될 것이라는 가정
SELECT o.order_id, i.book_id
FROM   order_items i, orders o
WHERE  i.order_id = o.order_id
AND    o.order_date = DATE '2026-09-15';

-- 고친 예: 순서를 강제하려면 LEADING 힌트로 명시한다
SELECT /*+ LEADING(o i) USE_NL(o i) */
       o.order_id, i.book_id
FROM   order_items i, orders o
WHERE  i.order_id = o.order_id
AND    o.order_date = DATE '2026-09-15';

부등호 조인에 해시 조인을 기대하는 실수

해시 조인은 등가 조인에서만 성립한다. 기간이 겹치는 두 구간을 찾는 것처럼 부등호로 연결된 조인 조건에 해시 힌트를 줘도 옵티마이저는 이를 적용할 수 없어 무시하고 다른 방식을 고른다. 힌트가 반영됐는지는 실행계획으로 반드시 확인해야 한다.

-- 틀린 기대: 부등호 조인인데 해시 조인이 그대로 적용될 것이라 가정
SELECT /*+ USE_HASH(a b) */
       a.order_id, b.order_id
FROM   orders a, orders b
WHERE  a.order_date BETWEEN b.order_date - 1 AND b.order_date + 1
AND    a.order_id <> b.order_id;

-- 고친 예: 부등호 조인에는 NL이나 소트 머지 조인을 검토한다
SELECT /*+ USE_MERGE(a b) */
       a.order_id, b.order_id
FROM   orders a, orders b
WHERE  a.order_date BETWEEN b.order_date - 1 AND b.order_date + 1
AND    a.order_id <> b.order_id;

한눈에 보기

세 조인 방식 중 무엇을 고를지 판단하는 기준
조인 방식동작 요약유리한 상황유의점
NL 조인드라이빙 행마다 내부 인덱스를 반복 조회드라이빙 건수가 적고 내부에 좋은 인덱스가 있을 때드라이빙 건수가 커지면 반복 횟수도 비례해 커진다
소트 머지 조인양쪽을 정렬한 뒤 커서로 병합등가 조인이 안 되는 부등호 조인, 이미 정렬된 인덱스가 있을 때정렬 비용이 들고 TEMP를 쓸 수 있다
해시 조인작은 쪽으로 해시 테이블을 만들고 큰 쪽으로 탐색인덱스가 없는 대용량 등가 조인, 배치 처리빌드 입력이 메모리에 안 들어가면 비용이 급증한다

연습 문제

  1. NL 조인에서 드라이빙 테이블의 건수가 반복 조회 횟수와 어떤 관계에 있는지 한 문장으로 설명하라.
  2. 해시 조인에서 "빌드 입력"과 "프로브 입력"의 역할 차이를 설명하고, 왜 작은 쪽을 빌드 입력으로 세워야 하는지 적어라.
  3. FROM 절에 order_items를 orders보다 먼저 썼다. 이 순서만으로 조인 드라이빙 순서가 결정되는지, 결정되지 않는다면 무엇으로 순서를 지정하는지 적어라.
  4. 부등호 조인에 USE_HASH 힌트를 주었더니 실행계획에 HASH JOIN이 나타나지 않았다. 그 이유를 설명하라.

정답과 해설

1. 드라이빙 테이블의 건수만큼 내부 테이블에 대한 인덱스 조회가 반복된다. 드라이빙 건수가 늘면 반복 횟수도 같은 비율로 늘어나므로, NL 조인의 비용은 드라이빙 건수에 비례한다.

2. 빌드 입력은 조인 키로 해시 테이블을 만드는 쪽이고, 프로브 입력은 그 해시 테이블을 조회하며 짝을 찾는 쪽이다. 빌드 입력이 작아야 해시 테이블이 PGA 메모리 안에 다 들어가 한 번의 스캔으로 조인이 끝난다. 빌드 입력이 크면 해시 버킷 일부가 TEMP로 내려가는 멀티패스가 생겨 비용이 크게 늘어난다.

3. 결정되지 않는다. Oracle 옵티마이저는 FROM 절 나열 순서를 조인 순서로 쓰지 않고 통계를 근거로 스스로 판단한다. 순서를 특정하고 싶으면 LEADING 힌트로 드라이빙 순서를 명시해야 한다.

4. 해시 조인은 등가 조인에서만 성립하는 물리적 조인 방식이다. 부등호로 연결된 조인 조건은 해시 테이블 조회 자체가 불가능하므로, 힌트를 주어도 옵티마이저가 이를 적용하지 못하고 다른 방식(NL 또는 소트 머지)을 선택한다.

오탈자·오류 제보 비공개로 접수되어 원고 수정에 반영됩니다

이메일 등 개인정보는 받지 않습니다. 답변이 필요한 질문은 아래 댓글을 이용해 주세요.

READER FEEDBACK

질문·의견

내용에 관한 질문이나 더 나은 설명을 위한 의견을 남겨 주세요. 오탈자는 위의 제보 양식이 더 빨리 반영됩니다. 이 댓글은 원래 게시글과 같은 자리에 쌓입니다.

댓글 0

아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.

댓글을 남기려면 로그인이 필요합니다.