/ DJANGO

(Django) select_related()와 prefetch_related()


프로젝트를 진행하면서 화면 로딩이 느린 문제가 발생했다. 이를 해결하기 위해 django-debug-toolbar를 설치하였다. 이를 통해 페이지가 로딩되면서 발생하는 쿼리를 확인해 보았다. 그 결과 3개의 데이터를 조회하는데 중복과 비슷한 쿼리가 굉장히 많이 발생하는 것을 볼 수 있었다. 쿼리의 중복을 없애고 쿼리를 최소화 할 수 있는 방법으로 위 2개의 메소드를 사용하는 것이였다.

marketsqltoolbar

  • 정참조 ForeignKey, OneToOneField 관계와 역참조 OneToOneField에서 활용한다.
  • 데이터베이스단에서 inner join으로 쿼리가 수행된다.

어떻게 활용하는가

toolbar를 읽어보면 어디부분에서 쿼리가 중복으로 발생했는지 볼 수 있다. 템플릿을 보면 article인스턴스가 1:n 관계인 user의 writer 필드에 접근하기 때문에 articles의 개수만큼 쿼리가 중복으로 발생한다.


 {% for article in articles %}
 <strong>{{article.writer.nickname}}</strong></span>



Article 객체를 쿼리로 반환하는 과정에서 Article객체에서 1:n으로 물고있는 User 모델의 writer 속성을 미리 로딩하여 캐싱하게 된다.

article_list = Article.objects.filter(is_deleted = False).select_related('writer')


단순 쿼리와의 차이점
단순쿼리의 경우 , 아래와 같이 작성하게 된다면 article 객체만 쿼리하기 때문에 각각 writer와 category를 얻기 위해서 다시 쿼리에 접근한다.

article = Article.objects.get(id=1)
writer = article.writer
category = article.category

select_related를 사용하게 되면 article 객체만 쿼리를 해도 sql에서 미리 category와 writer를 join하여 캐싱한다.

article_list = Article.objects.filter(is_deleted = False).select_related('writer', 'category')


  • 정참조 ManyToMany, 역참조 ForeignKey, ManyToMany에서 활용한다.
  • select_related()와 역할은 비슷하지만 방식이 좀 다르다
  • sql문에서 join이 되지 않고 각 관계별로 데이터베이스 쿼리를 수행하고 파이썬단에서 join을 수행한다

comment는 article에서 역참조를 하고 있는 테이블이다. 해당 속성들을 미리 로딩하기 위해 prefetch_related를 사용한다

<div class="social">
  <span class="social-comment">댓글 + </span>
</div>
article_list = Article.objects.filter(is_deleted = False).prefetch_related('comment')


위의 로직을 수행한 결과 10개의 쿼리로 줄어들었으며 중복이 제거 된 것을 볼 수 있다

gorf