인덱스를 이용한 조인 방식
ex) NL 조인 예시 

① 사원_X1 인덱스에서 입사일자>='19960101'인 첫번째 레코드를 찾아간다.
② 인덱스를 읽어 ROWID로 사원 테이블 레코드를 찾아간다.
③ 사원 테이블에서 읽은 사원번호 '0006'으로 고객_X1 인덱스를 탐색한다.
④ 고객_X1 인덱스에서 읽은 ROWID로 고객 테이블 레코드를 찾아간다.
랜덤 액세스 위주의 조인 방식
-> 레코드 하나를 읽기 위해 블록을 통째로 읽음
-> 대량 데이터를 조인할 때 불리함
한 레코드씩 순차적으로 진행
-> 부분범위 처리를 활용하면 큰 테이블을 조인하더라도 응답속도 빠름
인덱스 구성 전략이 특히 중요
-> 조인 컬럼에 대한 인덱스가 있느냐 없느냐, 있다면 어떻게 구성되었느냐에 따라 조인 효율이 크게 달라진다.
종합: NL 조인은 소량 데이터를 주로 처리하거나, 부분범위 처리가 가능한 OLTP 시스템에 적합한 조인 방식이다.
NL 조인을 제어할 때는 use_nl 힌트를 사용한다.

이때 ordered 힌트는 FROM절에 기술한 순서대로 조인하라는 지시이다.
따라서 위 쿼리에서는 e (e.입사일자 >='19960101') -> c (c.관리사원번호 = e.사원번호) 순서로 조건절을 비교할 것이다.

위 쿼리와 인덱스를 보고 어떻게 재구성하면 좋을지 스스로 진단해보자.
힌트는 아래와 같다.
order by에 의해 한번 더 정렬이 발생한다. 이를 방지하기 위해서는 STC_DT의 순서를 2번째로 재구성해야할 것 같다. (PRA_HST_STC_N1 : SALE_ORG_ID + STC_DT + STRD_GRP_ID + STRD_ID)PRA_HST_STC_N1 인덱스에 모든 컬럼이 존재할 필요는 없다. 그러므로 필수 조건인 SALE_ORG_ID와 정렬을 위한 STC_DT를 제외한 STRD_GRP_ID와 STRD_ID 컬럼은 인덱스에서 제외되어도 성능에 영향을 미치지 않는다. 오히려 테이블 b에 STRD_GRP_ID + STRD_ID 인덱스를 추가하는 것이 좋아보인다.PRA_HST_STC_N1 : SALE_ORG_ID + STC_DT로 기존 인덱스를 재구성하고,STRD_GRP_ID + STRD_ID로 구성된 새로운 인덱스를 b 테이블에 추가하는 것이 좋아보인다.
소트 머지 조인은 소트 단계, 머지 단계로 나누어 진행한다.
소트 단계: 양쪽 집합을 조인 컬럼을 기준으로 정렬
머지 단계: 정렬한 양쪽 집합을 서로 머지(Merge)
ex) 소트 머지 조인 예시 
① 조건에 해당하는 사원 데이터를 읽어 조인컬럼인 사원번호 순으로 정렬. 정렬한 결과집합은 PGA 영역에 할당된 Sort Area에 저장
② 조건에 해당하는 고객 데이터를 읽어 조인컬럼인 관리사원번호 순으로 정렬. 정렬한 결과집합은 PGA 영역에 할당된 Sort Area에 저장
③ PGA에 저장한 사원 데이터를 스캔하면서 PGA에 저장한 고객 데이터와 조인
①, ②는 소트 단계, ③은 머지단계이다. 머지 단계의 프로세싱은 NL 조인과 다르지 않다.
Sort Area에 저장한 데이터 자체가 인덱스 역할을 하므로 인덱스가 없어도 사용할 수 있는 조인 방식이다.
소트 머지 조인은 양쪽 테이블로부터 조인 대상 집합을 일괄적으로 읽어 PGA에 저장한 후 조인한다.
PGA는 프로세스만을 위한 독립적인 메모리 공간이므로 데이터를 읽을 때 래치 획득 과정이 없다.
또한 양쪽 집합이 모두 정렬되어 있기 때문에 테이블을 Full Scan하지 않아도 된다.
따라서 소트 머지 조인은 대량 데이터 조인에 유리하다.
하지만 소트 머지 조인도 조인 대상 집합을 읽을 때는 DB 버퍼 캐시를 경유한다. 이때는 인덱스를 이용하기도 한다. 이 과정에서 발생하는 버퍼 캐시 탐색 비용과 랜덤 액세스 부하는 피할 수 없다.
NL 조인은 인덱스 구성에 따른 성능 차이가 심하다. 또한 랜덤 I/O 때문에 대량 데이터 처리에 불리하고, 버퍼캐시 히트율에 따라 불규칙한 성능을 보인다.
반면 해시 조인은 조인 과정에 인덱스를 이용하지 않기 때문에 대량 데이터 조인에 유리하고, 일정한 성능을 보인다.
해시 조인은 Build, Probe 단계로 나뉘어 진행된다.
Build 단계: 작은 쪽 테이블(Build Input)을 읽어 해시 테이블(해시 맵)을 생성
Probe 단계: 큰 쪽 테이블(Probe Input)을 읽어 해시 테이블을 탐색하면서 조인
해시 테이블은 PGA 영역에 할당된 Hash Area에 저장한다. 만약 해시 테이블이 너무 커 PGA에 담을 수 없으면 Temp 테이블 스페이스에 저장한다.
ex) 해시 조인 예시 


① Build 단계, 조건에 해당하는 사원 데이터를 읽어 해시 테이블을 생성한다. 이때 조인 컬럼인 사원번호를 해시 테이블 키 값으로 사용한다. 즉, 사원번호를 해시 함수에 입력해서 반환된 값으로 해시 체인을 찾고, 그 해시 체인에 데이터를 연결한다.
② Probe 단계, 조건에 해당하는 고객 데이터를 하나씩 읽어 Build 단계에서 생성한 해시 테이블을 탐색한다. 즉, 관리사원번호를 해시 함수에 입력해서 반환된 값으로 해시 체인을 찾고, 그 해시 체인에서 스캔하여 값이 같은 사원번호를 찾는다.
②번 Probe 단계의 프로세싱은 NL 조인과 다르지 않다.

그림처럼 두 테이블 모두 대용량 테이블이어서 인메모리 해시 조인이 불가능한 상황도 존재한다. 이럴 때는 파티션 단계, 조인 단계로 나누어 작업을 처리한다. 쉽게 말해 분할정복 방식이다.

① 파티션 단계
② 조인 단계
해시 테이블은 PGA영역의 Hash Area에 할당되기 때문에 래치 획득 과정 없이 빠르게 데이터를 탐색, 조인할 수 있다.
해시 조인도 Build Input과 Probe Input 각 테이블을 읽을 때는 DB 버퍼 캐시를 경유한다. 이때는 인덱스를 이용하기도 한다. 이 과정에서 발생하는 버퍼 캐시 탐색 비용과 랜덤 액세스 부하는 피할 수 없다.
똑같이 PGA 영역을 사용하는 소트 머지 조인은 양쪽 테이블을 모두 정렬하여야 한다. 그렇기 때문에 PGA가 부족해 Temp 테이블 스페이스(디스크)에 쓰는 작업을 보통 수반한다. 반면 해시 조인은 작은 쪽 테이블을 해시 맵으로 만들기 때문에 Temp 테이블 스페이스에 쓰는 작업이 보통 일어나지 않는다.
설령 Build Input이 Hash Area 크기를 초과하여 Temp 테이블 스페이스를 사용하더라도 대량 데이터 조인 시에는 일반적으로 해시 조인이 가장 빠르다.
일반적인 메서드 선택 기준
여기서 소량과 대량은 단순히 데이터량의 많고 적음이 아닌 랜덤 액세스가 많음을 의미함.
수행빈도가 높은 쿼리에 대한 메서드 선택 기준
해시 조인이 빠름에도 NL 조인을 항상 최우선으로 사용하는 이유는 해시 테이블은 단 하나의 쿠리를 위해 생성되고 조인이 끝나면 곧바로 소멸하는 자료구조이기 때문이다. 반면 NL 조인이 사용하는 인덱스는 영구적으로 유지되면서 다양한 쿼리를 위해 공유 및 재사용되는 자료구조이다.
또한 해시 조인은 CPU와 메모리 사용률이 높고, 해시 맵을 만드는 과정에서 여러 가지 래치 경합이 발생할 수 있다.
따라서 해시 조인은 수행 빈도가 낮고, 쿼리 수행 시간이 오래걸리는 대량 데이터 조인할 때 사용하는 것이 적절하다.
서브쿼리: 하나의 SQL문 안에 괄호로 묶은 별도의 쿼리 블록, 쿼리에 내장된 또 다른 쿼리
오라클은 서브쿼리를 스칼라 서브쿼리, 인라인 뷰, 중첩된 서브쿼리로 분류한다.

스칼라 서브쿼리(Scalar Subquery): 한 레코드당 정확히 하나의 값을 반환하는 서브쿼리
인라인 뷰(Inline View): FROM절에 사용한 서브쿼리
중첩된 서브쿼리(Nested Subquery): 결과집합을 한정하기 위해 WHERE절에 사용한 서브쿼리
옵티마이저는 쿼리 블록 단위로 최적화를 수행한다. 따라서 서브쿼리를 사용하면 메인 쿼리와 서브 쿼리를 각각 따로 최적화한다.
서브쿼리별로 최적화한 쿼리는 전체적으로 최적화됐다고 말할수 없다. 따라서 SQL을 최적화 할때는 먼저 서브쿼리를 푸는 작업이 필요하다.

FILTER를 NESTED LOOP라고 생각하면 된다.| Filter 방식 | Unnesting 방식 | |
|---|---|---|
| 힌트 | no_unnest | unnest |
| Outer와 Inner 쿼리 | 항상 서브 쿼리가 Inner | 힌트를 이용해 선택 가능 |
| 조인 방식 | NL 조인 방식 | NL조인, 해시 조인 등 다양하게 가능 |
push_subq/no_push_subq 힌트로 제어push_subq는 항상 no_unnest와 함께 기술no_unnest와 no_push_subq를 함께 기술merge 힌트를 사용하여 뷰를 메인쿼리와 머징하는 방법
서브쿼리나 인라인 뷰를 조인 형태로 처리하는 방식
이를 통해 불필요한 임시 테이블 생성이나 필터링을 피할 수 있다.
ex) 
위 예시에서 고객 테이블에서 '전월 이후 가입한 고객'을 필터링하는 조건이 인라인 뷰 바깥에 있다.
하지만 인라인 뷰 안에서는 당월에 거래한 '모든' 고객의 거래 데이터를 읽어야 한다. 이는 매우 비효율적이다.

위와 같이 merge 힌트를 이용와 뷰를 메인쿼리와 머징

실행계획을 보면 쿼리가 위와 같이 변환되었음을 예측할 수 있다.(조인)
전체 데이터를 병합하는 방식으로 동작하므로 부분 범위 처리가 불가능하다


Group By 수행t.고객번호 = c.고객번호 조건을 인라인 뷰를 조인하기 전에 미리 처리하도록 변경한다.push_pred 힌트로 제어하며, 옵티마이저가 뷰를 머징하면 힌트가 작동하지 않으니 no_merge와 함께 사용해야 한다._optimizer_unnest_scalar_sq 파라미터를 false로 설정하여 기능을 끌 수 있다.