Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
227 changes: 227 additions & 0 deletions 4장_데이터베이스/조인원리.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
# 중첩 루프 조인 NESTED LOOPS JOIN

NLJ, Nested Loop Join 은 중첩 for문과 같은 원리로 조건에 맞는 조인을 하는 방법이다.

NESTED LOOP JOIN은 Driving Table로 한 테이블을 선정하고 이 테이블로부터 where절에 정의된 검색 조건을 만족하는 데이터들을 걸러낸 후, 이 값을 가지고 조인 대상 테이블을 반복적으로 검색하면서 조인 조건을 만족하는 최종 결과값을 얻어낸다.

### Driving Table , Driven Table

Driving Table이란 JOIN을 할 때 먼저 액세스 되어 주도하는 테이블이다. 즉 조인을 할 때 먼저 액세스 되는 테이블을 Driving Table이라 하고, 나중에 액세스 되는 테이블을 Driven Table 이라 한다. 결정 방식은 옵티마이저(효율적인 방법으로 SQL 수행할 최적의 처리 경로를 생성해주는 DBMS의 핵심 엔진)가 결정하게 된다.

### 동작 방식

아래와 같은 SQL이 있다고 가정해보자

```
SELECT /*+ ordered use_nl(e) */
e.empno, e.ename, d.dname, e.job, e.sal
FROM dept d, emp e
WHERE e.deptno = d.deptno ...........(1)
AND d.loc = 'SEOUL' ...........(2)
AND d.gb = '2' ...........(3)
AND e.sal >= 1500 ...........(4)
ORDER BY sal desc;
```

위 SQL이 중첩루프방식으로 JOIN이 수행되는 과정을 살펴보자

![Untitled](https://user-images.githubusercontent.com/48992412/201366774-8b9ad25b-297e-4325-b8ef-0629424ee849.png)

동작 방식은 위의 그림과 같고 번호 순서대로 동작한다.

```java
for(i=0; i<dept.len; i++) // driving table
for(j=0; j<emp.len; j++) // driven table
```

동작 순서를 보시면 아시겠지만 위와 같은 이중 for문과 작동원리는 비슷하다.

위의 그림에서 볼 수 있듯이 dept의 데이터를 추출하기 위해 dept_loc_idx라는 인덱스를 사용하여 loc = 'SEOUL' 조건을 먼저 걸러냈고, 이후 gb = '2'인 데이터를 추출하였으며, 이렇게 검색된 데이터를 가지고 같은 deptno를 가지는 사원들의 정보를 emp_deptno_idx라는 인덱스를 사용하여 sal >=1500 조건으로 emp Table을 조회하였다.

이렇듯 NESTED LOOP JOIN의 동작 방식은 Driving Table의 처리 범위를 하나씩 액세스 하면서 추출된 값으로 Driven Table을 조인하는 방식으로 동작하게 된다.

### **NESTED LOOPS JOIN의 장단점**

**1.** 인덱스에 의한 랜덤 액세스에 기반하고 있기 때문에 대량의 데이터 처리 시 적합하지 않다.

**2.** Driving Table로는 데이터가 적거나 where절 조건으로 row의 숫자를 줄일 수 있는 테이블이어야 한다.

**3.** Driven Table에는 조인을 위한 적절한 인덱스가 생성되어 있어야 한다.

**4.** 선행 테이블의 결과를 통해 후행 테이블을 액세스 할 때 랜덤 I/O가 발생한다.



## NESTED LOOPS JOIN의 성능개선

### 적절한 Driving Table 선정

NESTED LOOP JOIN을 할때는 어떤 테이블이 먼저 액세스 되느냐에 따라서 속도의 차이가 크게 난다. 앞서 NESTED LOOP JOIN 동작 과정에서 살펴보았듯이 먼저 액세스 되는 Driving Table의 조건을 만족하는 결과 row수가 많다면 그만큼 반복해서 Driven Table에 접근해야 하므로 성능은 자연히 나빠질 것이다.

따라서 NESTED LOOP JOIN방식을 채택하였다면 Driving Table의 선택이 매우 중요하다. 그렇기에 Driving Table은 WHERE 절로 최대한의 데이터를 거를 수 있는 테이블이나 애초에 데이터의 양이 적은 테이블로 선정하는 것이 좋다.



### Drivien Table의 Join 조건에 대한 인덱스 존재 유무

Driven Table의 조인 조건으로 사용될 칼럼에 인덱스가 존재하는지 여부도 성능에 매우 큰 영향을 준다. Driven Table에 인덱스가 존재하지 않는다면 Driving Table에서 도출된 결과와 맞는지를 FULL TABLE SCAN 방식을 사용하기 때문이다.

Driven Table의 Join 컬럼에 인덱스가 생성되지 않았다면 인덱스 생성을 고려해 보는 것이 좋고 그것이 어렵다면 조인 방식을 SORT / MERGE 방식등 다른 방식으로 바꾸는것이 성능 향상에 도움이 된다.



# 정렬 병합 조인

조회의 범위가 많을 때 주로 사용하는 조인 방법론이며 양쪽 테이블을 각각 Access 하여 그 결과를 정렬하고 그 정렬한 결과를 차례로 스캔해 나가면서 연결고리의 조건으로 Merge를 하는 방식이다.



### 사용 조건

**1.** 연결 고리에 인덱스가 전혀 없는 경우

**2.** 대용량의 자료를 조인할때 유리한 경우

**3.** 조인 조건으로 <, >, <=, >=와 같은 범위 비교 연산자가 사용된 경우

**4.** 인덱스 사용에 따른 랜덤 액세스의 오버헤드가 많은 경우



### 동작방식

아래와 같은 SQL이 있다고 가정해보자

```java
select /**USER_MERGE(A B) */ A.Color, B.SIZE,...
from TABLE_A A,TABLE_B B
where a.joinkey_a = b.joinkey_b -- join key에 대한 인덱스가 테이블 둘 모두 다 없음
and a.color = 'RED' --인덱스 있음
and b.size = 'MED'; --인덱스 없음
```

위와 같은 쿼리에 color칼럼에만 인덱스가 있다고 가정하였을 때 SORT MERGE JOIN로 이 쿼리가 동작된다면 아래와 같은 프로세스로 동작하게 된다.

![Untitled](https://user-images.githubusercontent.com/48992412/201481107-9b70d098-515d-4212-8f35-2b0615102aef.png)

먼저 왼쪽과 오른쪽에 있는 TABLE_A와 TABLE_B를 동시에 ACCESS 한다.

여기서 COLOR에 인덱스가 걸려있기에 TABLE_A는 인덱스 스캔을 할 것이고 TABLE_B는 테이블 풀스캔을 한다. 이렇게 조회된 데이터들은 TABLE_A에서 읽은 데이터는 JOINKEY_A를 기준으로, TABLE_B에서 읽은 데이터는 JOINKEY_B를 통해 별도의 공간에서 SORT 작업을 거치게 된다.

두 개의 정렬 작업이 모두 완료되었다면 정렬한 결과를 차례로 Scan 해 나가면서 연결고리의 조건으로 Merge 하여 리턴하게 된다.

위에서 설명한 SORT MERGE JOIN의 동작 방식의 내용을 간단하게 다시 정리하자면 아래의 3단계로 이루어진다고 생각하면 된다.

**1.** 각 테이블에 대해 동시에 독립적으로 데이터를 먼저 읽어 들인다.

**2.** 읽혀진 각 테이블의 데이터를 조인을 위한 연결고리에 대하여 정렬을 수행한다.

**3.** 정렬이 모두 끝난 후에 조인 작업이 수행한다.



## **SORT MERGE JOIN의 성능개선**

### 정렬속도 향상

SORT MERGE JOIN은 조인할 테이블의 데이터를 정렬시키는데, 만약 조인 컬럼이 정렬이 되어 있다면 좀 더 빨라질 것이다.



### SORT_AREA_SIZE 최적화

SORT MERGE JOIN은 두 테이블 간의 비교가 이루어지기 전에 수행하는 정렬 작업을 위해 별도의 정렬 공간이 필요하며 이 공간은 SORT_AREA_SIZE 크기만큼 메모리를 할당받아 사용하게 되고, 메모리가 부족하다면 Temporary Table Space를 이용하여 정렬을 수행하게 된다. 이때 Temporary Table Space를 사용하면 딜레이가 생기므로 SORT_AREA_SIZE를 적당한 크기로 설정해두는 것이 속도 향상에 도움이 된다.



# 해시 조인 HASH JOIN

HASH 조인은 조인될 두 테이블 중 하나를 해시 테이블로 선정하여 조인될 테이블의 조인 키 값을 해시 알고리즘으로 비교하여 매치되는 결과값을 얻는 방식이다.

HASH JOIN은 비용 기반 옵티마이저를 사용할 때만 사용될 수 있는 조인 방식이며 '=' 비교를 통한 조인에서만 사용될 수 있고, 주로 많은 양의 데이터를 조인해야 하는 경우에 주로 사용된다.

### 사용 조건

**1.** JOIN 컬럼에 적당한 인덱스가 없어 NL JOIN이 비효율적일 때

**2.** JOIN Access량이 많아 Random Access 부하가 심하여 NL JOIN이 비효율적일 때

**3.** Sort Merge Join을 하기에는 두 테이블이 너무 커 Sort 부하가 심할 때

**4.** 수행빈도가 낮고 쿼리 수행 시간이 오래 걸리는 대용량 테이블을 JOIN 할 때

### 동작 방식

![Untitled](https://user-images.githubusercontent.com/48992412/201481062-62a51172-cfe8-4cf2-9ed7-2f467363df4e.png)

**1.** 둘 중 작은 집합(Build Input)을 읽어 Hash Area에 해시 테이블을 생성한다. (해시 함수에서 리턴 받은 버킷 주소로 찾아가 해시 체인에 엔트리를 연결)

**2.** 반대쪽 큰 집합(Probe Input)을 읽어 해시 테이블을 탐색하면서 JOIN 한다.

**3.** 해시 함수에서 리턴 받은 버킷 주소로 찾아가 해시 체인을 스캔하면서 데이터를 찾는다.



### HASH **JOIN의 장단점**

1. NL조인처럼 조인 과정에서 발생하는 Ramdom 액세스 부하가 없다.(단, 양쪽집합을 읽는 과장에서 인덱스를 이용한다면 Random 엑세스 발생)
2. 소트 머지 조인 처럼 조인 전에 미리 양쪽 집할을 정렬하는 부담도 없다.
3. NL조인과 달리 래치획등 과정없이 PGA에서 빠르게 데이터를 탐
4. 가장 비용이 많이 들어가는 조인방법으로 Driving Table이 작을 때 효과적이다.
5. 드라이빙 조건과 상관없이 좋은 성능을 발휘할 수 있는 조인 방법이며 대체로 제일 빠르다.



## **HASH JOIN의 성능 개선 포인트**

### HASH TABLE을 만드는 과정을 효율화 한다.

HASH JOIN은 해시 테이블을 생성하는 비용이 수반되므로 이 과정을 효율화하는 것이 성능 개선에 있어 가장 중요하다. 그렇기에 HASH TABLE로 만들 Build Input이 Hash Area에 담길 정도로 충분히 작아야 하며 Build Input 해시 키 칼럼에 중복 값이 거의 없어야 효율적인 동작을 기대할 수 있다.



### CPU의 성능을 향상한다.

HASH BUCKET이 조인 집합에 구성되어 해시 함수 결과를 저장해야 하는데 기본적으로HASH_AREA_SIZE에 지정된 크기만큼의 메모리가 할당되어 사용된다. 이러한 처리에는 많은 메모리와 CPU 자원을 소모하게 된다. 그렇기에 CPU의 자원이 넉넉하다면 다른 조인에 비해 보다 좋은 효율을 내지만 부족한 상황에서는 다른 조인 방법보다 느려질 수도 있다. 그러므로 CPU의 성능을 향상한다면 HASH JOIN의 성능을 향상할 수 있다.



- **HASH_AREA_SIZE**

HASH JOIN에 사용되는 최대 메모리 SIZE를 지정하는 설정값입니다. Hash Join에서 사용되는 해쉬 메모리 크기(HASH_AREA_SIZE)의 기본 값은 SORT_AREA_SIZE의 2배입니다. 9i 이상에서 값을 지정하는 것을 권장하지 않고, PGA_AGGREGATE_TARGET parameter 사용을 권장합니다.































## REFERENCE

[ 데이터베이스 NESTED LOOPS JOIN (중첩 루프 조인)에 대하여] - 코딩팩토리 - https://coding-factory.tistory.com/756

구루비 지식창고 - http://wiki.gurubee.net/pages/viewpage.action?pageId=28116163