1. SELECT 의 기본 구조 



Select 구문의 순서는 위의 그림과 같다.


    Select           //조회할것이다.

    *                   // 어떤 데이터를

    from  Table  // 테이블로 부터

    where          // 어떤 조건을 가진

    group by      // 어떤데이터들을  특정 column기준으로 묶는다.

    having          // 특정 조건을 가지고 있는 row 만 추출한다

    Order By     // 해당 row 들을 어떤 컬럼을 기준으로 정렬할 것이다



2. Join 이란 





SQL 에서 다양한 조인 연산이 존재한다. 


Cross join

inner join

outer join

self join




이들중 기능적인 관점으로 분류한 것은 


Cross Join

Inner Join

Outer Join 


이렇게 3가지 인데 


Cross join 은 사실상 안쓰이는 거니 논외로 친다.


2.1 INNER JOIN

조인되는 두 테이블 모두에 데이터가 존재해야 row를 가져온다.

두 테이블중 한 곳이라도 NULL 이면 데이터를 조회 하지 않는다.


select * from Employees E 

inner join Departments D

on E.dept_id=D.dept_id


2.2 OUTER JOIN (with LEFT RIGHT JOIN)

FULL OUTER 

조인 이 되는 두 테이블에서 NULL을 허용한다. 

이렇게 쓰는 경우는 잘없음


LEFT OUTER   

구동 테이블의 NULL을 허용하지 않음


RIGHT OUTER

내부 테이블의 NULL을 허용하지 않음 








성능을 고려한 알고리즘 

1. Nested Loops 

SQL에서 JOIN은 한번에 두개의 테이블만 결합 하므로 본질적으로 이중 반복과 같은 의미이다.


그렇기 때문에 Join 쿼리의 수행시간은 table(A)* table(B)  이다. 


여기서 접근하는 레코드의 수는 A * B 와 B * A 는 서로 같다 

그러나 실제 성능에서는  좀 다른 의미를 가질수 있다. 


두테이블이 서로 조인할때 결합키에 인덱스가 존재한다면 해당 인덱스를 통해 DBMS는 내부 테이블을 완전히 순환 하지 않아도 된다.


이상적인 경우는 구동테이블 (from A 테이블)의 레코드 한개에 내부테이블(join B 테이블)이 

1대1 관계인 경우이다 .


이 경우 반복없이 


1. A 에서 row 조회 

2. 인덱싱으로 B 조회


A *2 번의 실행 시간 이 걸린다.


결론 :

 내부 테이블의 결합키에 인덱싱이 존재한다면 

구동테이블은 작은 테이블 

내부테이블은 큰 테이블 로  

조인 할수록 

내부테이블의 인덱싱을 탐색하여 반복 생략 효과가 커진다. 




1.2 Nested Loops 의 단점

히트되는 레코드가 너무 많은 경우 기대한 만큼의 응답시간이 안나오기도 한다.

예를 들어

점포테이블과 주문 테이블의 경우

하나의 점포에 대해 여러개의 주문이 대응하므로 

구동테이블은 작은 점포테이블

결합키는 점포테이블의 ID,

내부 테이블은 큰 주문테이블로 JOIN  할 경우 

위 설명대로라면 좋은 성능을 기대할수 있지만


한 점포당 수백 수천건의 주문 row 가 히트한다면 

반복 횟수가 너무 많아서 결국 성능이 떨어진다.


이런경우에는 해시를 통해 해결할수 있다.


2. HASH

해쉬 방식은 먼저 읽어들인 구동테이블의 ID 로 

해시 테이블을 만들고 이어서 내부테이블을 읽어 들여서 해시값이 일치하는지를 확인하는 방식이다.

이 경우 해시라는 방식 특성상 속도가 조회속도가 좋지만 문제는 

메모리를 크게 소모한다.

메모리가 부족하면 저장소를 사용하므로 지연이 발생한다.


유용한경우

    1. 구동테이블로 쓸만한 충분히 작은 테이블이 존재하지 않는경우
    2. 내부테이블이 매우 거대한경우 (ex = >점포 : 주문) 

Caution 


Hash 결합은 반드시 양쪽 테이블의 레코드를 전부 읽어야 하므로 

테이블 풀스캔이 사용되는 경우가 많다.

그렇기에 풀스캔에 걸리는 시간도 고려해봐야한다.




지연 로딩 과 즉시 로딩 (Lazy & Eager) 그리고 1+N 문제


장고는 Lazy Eager 하다는 표현을 안쓰고

쿼리셋에서 쓰는 함수들도 inner outer를 사용한다는 것을 유추할만한 네이밍이 없다.

좋다 나쁘다 할것없이 그냥 그러하다.


    select_related()
    • ForeignKey, OneToOneField 관계에서 활용
    • ForeignKey/OneToOneField 관계에서 Lazy하게 쿼리하지 않고, DB단에서 INNER JOIN 으로 쿼리할 수 있다.
  • prefetch_related()
    • ManyToManyField, ForeignKey의 reverse relation 에서 활용
    • 각 관계 별로 DB 쿼리를 수행하고, 파이썬 단에서 조인을 수행한다.
https://wayhome25.github.io/django/2017/06/20/selected_related_prefetch_related/



N+1 Query Problem
N+1 쿼리 문제, N+1 SELECT 문제, N+1 문제
  • 쿼리 1번으로 N건을 가져왔는데, 관련 컬럼을 얻기 위해 쿼리를 N번 추가 수행하는 문제
  • 쿼리결과 건수마다 참조 정보를 얻기 위해 건수만큼 반복해서 쿼리를 수행하게 되는 문제
  • DB쿼리 수행비용(횟수)이 크기 때문에, eager 로딩 등의 방법으로 해결하는 것이 권장됨





참고 문헌

  • http://www.sqlprogram.com/Basics/sql-join.aspx
  • SQL 레벨업 [저자 미크, 역자 윤인성]
  • 자바 ORM 표준 JPA 프로그래밍 [저자 김영한]
  • https://wayhome25.github.io/django/2017/06/20/selected_related_prefetch_related/




+ Recent posts