Django | select_related() & prefetch_related()

김민호·2021년 11월 8일
0

DJANGO

목록 보기
16/18

참고
장고 공식문서
https://leffept.tistory.com/312

select_related, prefetch_related는 하나의 쿼리셋을 가져올 때 연관되어 있는 objects들을 미리 불러오게(Eager Loading)하는 함수이다. JOIN문을 사용하기 때문에 호출되는 SQL문이 복잡해질 수 있지만, 이렇게 불러온 데이터들은앞서 알아본 result_cache 라는 부분에 cache 되기 때문에 결과적으로 중복 호출을 방지할 수 있다. 두 함수 모두 DB에 엑세스(connection)하는 횟수를 줄여주므로 Performance를 향상시킬 수 있다. 하지만 두 함수의 SQL 문이 호출되는 방식에는 차이가 있다.

  • select_related : JOIN을 통해 데이터를 즉시 가져오는 방법(Eager Loading) - SQL단계에서의 JOIN
  • prefetch_related : 추가 쿼리를 통해 데이터를 즉시 가져오는 방법(추가 쿼리 발생) - JOIN은 파이썬 level에서 이루어짐

select_related(*fields)

  • 정참조의 JOIN을 통해 Eager Loading하는 함수
  • 괄호 안에 fields를 써야 함

사용

  • 1) 1 : 1 관계 = OneToOneField
  • 2) 1 : N 관계 에서 ForeignKey를 물고 있는 N이 사용(=정참조)
    OneToOneField 인 경우 필드 이름을 지정하는 대신 models.py에서 related_name 속성을 사용해야 한다.
from django.db import models

class City(models.Model):
    # ...
    pass

class Person(models.Model):
    # ...
    hometown = models.ForeignKey(
        City,
        on_delete=models.SET_NULL,
        blank=True,
        null=True,
    )

class Book(models.Model):
    # ...
    author = models.ForeignKey(Person, on_delete=models.CASCADE)

위와 같은 모델이 있을 경우를 생각해보자

  • City
  • Person : city를 정참조하고 있다
  • Book : person을 정참조하고
    있다

City와 Perosn 관계

  • Person이 City를 (정)참조하고 있다
  • City와 Person의 관계는 1:N 이다
  • N에 해당하는 Person 모델에 City를 FK로 물고 있는 필드가 있다
  • Person -> City : 정참조
  • City -> Person : 역참조

Book 객체에서 hometown의 정보를 가져오기 위한 2가지 방법이 있다

select_relate()를 안쓰는 방법

b = Book.objects.get(id=4) # Hits the database.
p = b.author               # Hits the database.
c = p.hometown             # Hits the database.

이 경우 DB에 총 3번 요청하기 때문에 비효율적이다.

select_relate()를 쓰는 방법

b = Book.objects.select_related('author_hometown').get(id=4) # Hits the database.
p = b.author 
# Doesn't hit the database, because b.author has been prepopulated in the previous query.
c = p.hometown 

이 경우 select_related()로 정보를 이미 불러 두었기 때문에 총 한 번만 접근하면 된다

특징

  • 1.select_related()는 객체의 다른 모든 쿼리 세트와 함께 사용할 수 있다
for e in Entry.objects.filter(pub_date__gt=timezone.now()).select_related('blog'):
    # Without select_related(), this would make a database query for each
    # loop iteration in order to fetch the related blog for each entry.
    blogs.add(e.blog)
    1. filter()select_related()의 연결 순서는 상관없다
Entry.objects.filter(pub_date__gt=timezone.now()).select_related('blog')
Entry.objects.select_related('blog').filter(pub_date__gt=timezone.now())
    1. 여러 번 붙여 쓸 경우 두 가지 방법 모두 가능하다
select_related('foo', 'bar')
select_related('foo').select_related('bar')

prefetch_related(*look_ups)

사용

  • 1) 1 : N 관계 에서 1 이 사용
  • 2) M : N 관계

select_related와 비교

>>> post = Post.objects.select_related('category').all()
>>> for p in post:
...     p.category.name
... 
SELECT "mysite2_post"."id",
       "mysite2_post"."category_id",
       "mysite2_post"."title",
       "mysite2_post"."content",
       "mysite2_post"."created_at",
       "mysite2_post"."updated_at",
       "mysite2_category"."id",
       "mysite2_category"."blog_id",
       "mysite2_category"."name"
  FROM "mysite2_post"
  LEFT OUTER JOIN "mysite2_category"
    ON ("mysite2_post"."category_id" = "mysite2_category"."id")
Execution time: 0.007924s [Database: default]
'programming category'
'programming category'
'programming category'
'programming category'
'programming category'
>>> 

python
category = Category.objects.prefetch_related('post_set').get(name='django')
SELECT "mysite2_category"."id",
"mysite2_category"."blog_id",
"mysite2_category"."name"
FROM "mysite2_category"
WHERE "mysite2_category"."name" = 'django'
LIMIT 21
Execution time: 0.000285s [Database: default]
SELECT "mysite2_post"."id",
"mysite2_post"."category_id",
"mysite2_post"."title",
"mysite2_post"."content",
"mysite2_post"."created_at",
"mysite2_post"."updated_at"
FROM "mysite2_post"
WHERE "mysite2_post"."category_id" IN (6)
Execution time: 0.000296s [Database: default]
for c in category.post_set.all():
... c.title
...
'django 1'
'django 2'
'django 3'

  • select_related 와의 가장 큰 차이점은 추가 쿼리가 발생한다는 것이고, 또 다른 차이점은 발생된 추가 쿼리를 파이썬 단계에서 JOIN을 통해 결과를 만들어낸다는 점이다.
profile
개발자로서의 삶은 https://velog.io/@maxminos 에서 기록하고 있습니다 😀

0개의 댓글