Django select_related, prefetch_related

BLAKE KIM·2020년 8월 23일
0

select_related

정참조 관계에서 사용하며 JOIN 쿼리가 한 번만 수행된다. select_related로 함께 가져온 정보는 추후에 가지고 올 때 db쿼리를 하지 않음. 또한 정보가 MySQL상에서 JOIN한 후 넘어온다. 이와 달리 prefetch_related는 django python에서 JOIN이 실행된다.

>> python manage.py shell 에서 아래 코드 입력

# [1]
drink_all=Drink.objects.select_related('category').all()

# [2]
for drink in drink_all:
    print(drink.category.name)

[1]번이 실행되었을 때 결과로 DB Hit가 발생되는 것을 보여주는 log이다.

2020-04-20 13:22:34,741 DEBUG (0.000) SELECT "drinks"."id", "drinks"."name", "drinks"."menu_id", "drinks"."category_id", "drinks"."nutrition_id", "categories"."id", "categories"."name", "categories"."menu_id" FROM "drinks" LEFT OUTER JOIN "categories" ON ("drinks"."category_id" = "categories"."id"); args=()

[2]번이 실행되었을 때 결과로 이미 앞에서 JOIN하여 정보가 넘어왔기 때문에 DB Hit가 발생하지 않는 것을 알 수 있다.

콜드 브루 커피
콜드 브루 커피
콜드 브루 커피
콜드 브루 커피
콜드 브루 커피
브루드 커피
브루드 커피
에스프레소

for drink in drink_all:
    print(drink.menu.name)

select_related로 가져온 정보가 아니기 때문에 위 코드가 실행될 때는 print를 한 번 찍을 때마다 DB Hit가 진행된다. 즉 drink_all에 정보가 5개가 존재한다면 print는 5번 진행되고 DB Hit도 5번 하게 된다.

prefetch_related

select_related와 달리 ManyToOne 또는 ManyToMany 역참조 관계에서 별도의 2개의 쿼리를 수행한 후에 파이썬에서 JOIN한다.

prefetch_drink = Category.objects.prefetch_related('drink_set').get(id = 1)

위 코드가 실행되면 발생하는 쿼리로 2번의 쿼리가 실행되는 것을 알 수 있다.

2020-04-20 13:04:07,539 DEBUG (0.000) SELECT "categories"."id", "categories"."name", "categories"."menu_id" FROM "categories" WHERE "categories"."id" = 1 LIMIT 21; args=(1,)
2020-04-20 13:04:07,540 DEBUG (0.000) SELECT "drinks"."id", "drinks"."name", "drinks"."menu_id", "drinks"."category_id", "drinks"."nutrition_id" FROM "drinks" WHERE "drinks"."category_id" IN (1); args=(1,)

Django Document

원본 주소

select_related(*fields)

select_related는 Foreign_Key 관계를 따르는 QuerySet을 반환한다. select_related는 추가적으로 연결된 객체의 데이터를 가져오는 쿼리를 실행한다. 이것은 성능을 향상시키는 것이다. 하나의 쿼리를 실행할 때보다 복잡한 쿼리이지만 나중에 Foreign_Key관계를 사용할 때 DB 쿼리가 필요하지 않다는 뜻이다.

다음의 예는 일반적인 탐색과 select_related의 탐색의 차이를 설명한다. 먼저 일반적인 탐색의 예이다.

# DB Hit.
e = Entry.objects.get(id=5)

# 연결된 Blog 객체를 가져오기 위해 DB Hit한다.
b = e.blog

다음은 select_related의 탐색이다.

# DB Hit
e = Entry.objects.select_related('blog').get(id=5)

# DB Hit가 발생하지 않는다. e.blog는 앞의 쿼리에서 이미 채워졌기 때문이다.
b = e.blog

select_related()는 객체의 모든 queryset과 함께 사용될 수 있다.

from django.utils import timezone

# 미래에 포스팅되기로 예정된 항목들과 함께 모든 blog를 찾는다.
blogs = set()

for e in Entry.objects.filter(pub_date__gt=timezone.now()).select_related('blog'):
    # select_related()가 없었다면, 이것은 for문의 각 루프마다 DB 쿼리를 만들어 냈을 것이다.
    # 각 항목들과 연결된 blog를 가져오기 위해서
    blogs.add(e.blog)

filter()와 select_related()의 순서는 중요하지 않다. 다음 queryset들은 동일하다.

Entry.objects.filter(pub_date__gt=timezone.now()).select_related('blog')
Entry.objects.select_related('blog').filter(pub_date__gt=timezone.now())
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)

Book.objects.select_related('author__hometown').get(id = 4)를 호출하면 관련 Person과 관련 City가 캐시된다.

# Hits the database with joins to the author and hometown tables.
b = Book.objects.select_related('author__hometown').get(id=4)
p = b.author         # Doesn't hit the database.
c = p.hometown       # Doesn't hit the database.

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

모든 ForeignKey 또는 OneToOneField 관계를 select_related()에 전달된 필드 목록에서 참조할 수 있다. 또한 OneToOneField의 역참조 할때도 참조할 수 있다. 즉 OneToOneField를 필드가 정의된 객체에 대해 역참조 할 수 있다. 필드 이름을 지정하는 대신에, 연결된 객체의 필드의 related_name을 사용한다.

많은 관련 객체를 사용하여 select_related()를 호출하거나 모든 관계를 알지 못하는 상황이있을 수 있습니다. 이 경우 인수없이 select_related()를 호출 할 수 있습니다. 이는 찾을 수 있는 모든 nullable하지 않은 외래 키를 따릅니다. nullable한 것은 외래 키를 지정해야합니다. 기본 쿼리를 더 복잡하게 만들고 실제로 필요한 것보다 더 많은 데이터를 반환 할 수 있으므로 대부분의 경우 권장되지 않습니다.

QuerySet에서 과거 select_related 호출에 의해 추가된 관련 필드 목록을 지워야하는 경우 None을 매개 변수로 전달할 수 있습니다.

>>> without_relations = queryset.select_related(None)

select_related 호출 연결은 다른 메서드와 유사한 방식으로 작동합니다.

select_related('foo', 'bar')
select_related('foo').select_related('bar')

prefetch_reated(*lookups)

나중에 추가

profile
BackEnd

0개의 댓글