ORM QuerySet의 특징 포스트에 이어서 Eager Loading에 대해 알아보자. 💡
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/
'🐍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 |