Django select_related()와 prefetch_related()

장고의 select_related()와 prefetch_related()에 대해서

Django select_related()와 prefetch_related()

N+1

Django ORM에서 외래 키 사용하게 되면
쉽게 볼 수 있는 N+1문제가 있다

e = Entry.objects.get(id=5)

b = e.blog

위와 같은 코드에서

처음 e = Entry.objects.get(id=5)에서 쿼리가 한번 날라가게 되고

b = e.blog 에서도 쿼리가 한번 날라가게된다

만양 b = e.blog 해당 코드를 루프에서 N번 사용하게 된다면??

쿼리가 N+1번 날라가게 된다.
당근 쿼리가 많은 것은 좋지 않다.

e = Entry.objects.get(id=5).select_related("blog")

b = e.blog

이렇게 select_related()를 붙여준다면
처음 e = Entry.objects.get(id=5).select_related("blog")blog를 join하여 쿼리를 날리고 캐시한다.

그러면 다음 b = e.blog 이때는 쿼리가 날라가지 않게 된다.

이를 이용해 쿼리의 수를 줄일 수 있지만
join 무조건 하게 되어 쿼리 성능이 저하될 수 있다.

prefetch_related()

그래서 select_related() 대신 사용할 수 있는 방법이 prefetch_related()이다.

공식 문서에선

Returns a QuerySet that will automatically retrieve, in a single batch, related objects for each of the specified lookups.

별도의 쿼리로 싱글 배치로 연관 객체들을 불러온다고 보면 된다.

select_related()가 Join을 통해 연관 객체를 가져온다면
prefetch_related() 는 쿼리를 하나 더 날려서 가져온다.

그래서 별도의 쿼리로 가져와서 Python에서 Join하게 된다.

e = Entry.objects.get(id=5).prefetch_related("blog")

b = e.blog

이렇게 작성해도
b = e.blog에선 쿼리가 날라가지 않는다.

언제 쓰지.

기본적으로 둘다 N+1 문제의 경우 사용할 수 있다.

두 가지 중 어떤걸 사용할지 고민할때는

  • Join을 해도 괜찮은지?
  • 별도의 쿼리로 가져오는게 좋은지?
    를 고려하자

최근엔 select_related()를 사용할 때
inner join order by filesort로 Slow 쿼리가 발생한 이슈가 있었다.

그래서 Join을 하지 않고 연관 테이블의 데이터를 가져올때
prefetch_related()로 변경하니 정상적으로 쿼리가 변경되었다.