장고는 select_related()
라는 QuerySet 메서드를 제공한다. 이는 one-to-many 관계의 객체를 검색할 때 사용한다. 이는 복잡한 QuerySet이 될 수 있지만, 연관 관계에 있는 필드에 접근하기 위해 추가적인 쿼리를 날릴 필요가 없게된다. select_related()
메서드는 ForeignKey
와 OneToOne
필드에 사용한다. SQL의 JOIN
쿼리를 통해 연관 객체를 가져오게 된다.
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)
# 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.
만약 argument 없이 select_related()
를 호출하면, 모든 ForeignKey
관계의 객체를 검색할 것이다. 그렇기 때문에, 항상 호출 이후에 접근할 관계에 대해서만 select_related()
를 사용해야한다.
select_related()
를 사용하는 것은 실행 시간을 굉장히 증가시킬 수 있기 때문에 조심해서 사용해야한다.
select_related()
가 one-to-many 관계의 연관 객체들을 검색하는데 도움을 준다. 하지만 select_related()
는 many-to-many 또는 many-to-one 관계 (ManyToMany
또는 역(逆)ForeignKey
필드)에 대해서는 소용이 없다. 장고는 prefetch_related()
메서드를 제공한다. prefetch_related()
는 각 관계에 대해서 lookup을 나눠서하고, 파이썬을 이용해서 결과를 합친다. 이 메소드는 GenericRelation
과 GenericForeignKey
에 대한 prefetching도 지원한다.
class Topping(models.Model):
name = models.CharField(max_length=30)
class Pizza(models.Model):
name = models.CharField(max_length=50)
toppings = models.ManyToManyField(Topping)
class Restaurant(models.Model):
pizzas = models.ManyToManyField(Pizza, related_name='restaurants')
best_pizza = models.ForeignKey(Pizza, related_name='championed_by', on_delete=models.CASCADE)
>>> Restaurant.objects.prefetch_related('pizzas__toppings')
# This will result in a total of 3 database queries - one for the restaurants, one for the pizzas, and one for the toppings.
>>> Restaurant.objects.prefetch_related('best_pizza__toppings')
# 2 queries using select_related()
>>> Restaurant.objects.select_related('best_pizza').prefetch_related('best_pizza__toppings')