🐍Python | Django

[Django] ORM Eager Loading(select related & prefetch_related)

이줭 2022. 5. 15. 20:32
728x90

ORM QuerySet의 특징 포스트에 이어서 Eager Loading에 대해 알아보자. 💡

https://dev-jy.tistory.com/23

 

[Django] QuerySet?!

장고 쿼리셋의 특징 중 Lazy Loading, Caching, Eager Loading에 대해 알아보자. 🤔 Lazy Loading 먼저, ORM은 게으르다. 😴 from prod.models import Product product = Product.objects.fitler(name='제품A') pr..

dev-jy.tistory.com

Django ORM은 기본적으로 Lazy Loading 전략을 택하기 때문에, 아래의 예시처럼 N+1문제가 발생할 수 있다. 🤬

from board.models import Board
from board.models import Writer

def get_writer_name_list():
    boards = list(Board.objects.all()) # DB hit
    writer_name_list = [board.writer.name for board in boards] # DB hit * N
    
return writer_name_list

get_writer_name_list()

이를 해결하기 위해 Eager Loading을 사용할 수 있고, 옵션으로는 select_related()prefetch_related()가 있다.

select_related()

select_related()는 join문을 통해서 데이터를 즉시 로딩한다. 위의 코드를 수정해보자.

from board.models import Board
from board.models import Writer

def get_writer_name_list():
    boards = list(Board.objects.all().select_related('writer')) # DB hit
    writer_name_list = [board.writer.name for board in boards] 
    
return writer_name_list

get_writer_name_list()

N+1 문제가 발생한 코드를 위와 같이 수정하면 Board를 조회할 때 Writer에 대한 정보도 JOIN 하여 한번에 가져오기 때문에 추가 쿼리 없이 데이터를 가져올 수 있다. INNER JOIN을 통해 board테이블을 writer테이블과 함께 불러와 쿼리가 발생하지 않도록 해준다.

prefetch_related()

from board.models import Board
from board.models import Writer

def get_writer_name_list():
    boards = list(Board.objects.all().prefetch_related('writer')) # DB hit * 2
    writer_name_list = [board.writer.name for board in boards] 
    
return writer_name_list

get_writer_name_list()

prefetch_related()를 사용하면 추가적으로 하나의 쿼리문이 더 수행되면서 참조하고 있는 테이블의 정보를 전부 가져오게 된다. 

 

그래서 언제 어떤걸 쓰라고.. 🤔

 

select_related()정방향 참조 관계(OneToOne 관계 또는 OneToMany에서 Many)에서 사용하고, prefetch_related()역방향 참조 관계(select_related() 범위에서 더 나아가 OneToMany에서 One 또는 ManyToMany)에서 사용한다. 만약 ManyToMany 관계에서 select_related()를 사용하면 에러가 발생한다.

 

만약 select_related()를 사용할 수 있는 상황이라면 prefetch_related()에 비해 쿼리수가 하나 더 줄어드니 웬만하면 select_related()를 사용하자!

 

정방향 참조? 역방향 참조?

간단하게 설명하면, 식당:주문 = 1:N 관계일 때, 식당 입장에서 주문은 역방향 참조 모델이고 주문 입장에서 식당은 정방향 참조 모델이다.

 

정방향 참조 모델은 select_related(), prefetch_related() 모두에 옵션으로 줄 수 있지만, 역방향 참조 모델은 select_related()에 옵션으로 줄 수 없다.

 

 

참고 : https://blog.myungseokang.dev/posts/django-query-optimization/

728x90

'🐍Python | Django' 카테고리의 다른 글

[Python] lstrip(), rstrip(), strip()  (0) 2022.05.17
[Python] __new__ ? __init__ ?  (0) 2022.05.16
[Django] QuerySet method  (0) 2022.05.12
[Django] QuerySet?!  (0) 2022.05.12
[Python] generator  (0) 2022.05.09