장고는 특정 시기에만 쿼리를 날린다.
그 시기를 알아야 최적화가 가능하다.
for문을 돌때, 조회할 때마다 sql이 계속 호출되는 문제를 N+1문제라고 한다.
(유저를 호출하는 sql 한번) + (유저 개수 N) = (N+1 개의 쿼리)
이 문제를 해결할 수 있는 아름다운 방법을 소개하고자 한다.
select_related(*fields)
# database에 쿼리를 날린다.
e = Entry.objects.get(id=5)
# Entry에 연관된 blog의 정보를 불러오기 위해 데이터베이스에 한번 더 요청
b = e.blog
위의 쿼리는 database에 2번 요청 위의 쿼리를 select_related로 최적화했을 때 1번의 쿼리요청만으로 원하는 내용을 불러올 수 있다.
# database에 쿼리를 날린다.
e = Entry.objects.select_related('blog').get(id=5)
# database에 쿼리를 날리지않는다. (이미 첫번째 쿼리에서 e.blog를 불러왔기 때문)
b = e.blog
from django.utils import timezone
blogs = set()
for e in Entry.objects.filter(pub_date__gt=timezone.now()).select_related('blog'):
blogs.add(e.blog)
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())
# 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.
첫번째 select_related를 사용한 경우는 b.author.hometown을 전부 다 받아온다.
두번째 select_related를 사용하지 않은 경우 author, hometown을 각각 불러올 때마다 database에 새로운 쿼리를 요청한다.
prefetch_related(*lookups)
select_related vs prefetch_related
Pizza.objects.all().prefetch_related('toppings')
가능한 쿼리목록
Restaurant.objects.prefetch_related('pizzas__toppings')
# 레스토랑, 피자, 토핑에 대해서 3개의 쿼리 생성
Restaurant.objects.prefetch_related('best_pizza__toppings')
# 쿼리수를 2개로 줄이기 위해 select_related도 사용
Restaurant.objects.select_related('best_pizza').prefetch_related('best_pizza__toppings')
Prefetch명령어를 사용해서 추가적인 prefetch operation을 조정할 수 있다.
다음과 같이 사용할 수 있으며 order_by와 같은 작업을 통해 순서를 바꿔주는 등의 작업을 할 수 있다.
또한 Prefetch안에 select_related도 넣어줄 수 있다.
Restaurant.objects.prefetch_related(Prefetch('pizzas__toppings'))
Restaurant.objects.prefetch_related(
Prefetch('pizzas__toppings', queryset=Toppings.objects.order_by('name')))
Pizza.objects.prefetch_related(
Prefetch('restaurants', queryset=Restaurant.objects.select_related('best_pizza')))
prefetch_related를 사용하면 select_related와 마찬가지로 중복되는 쿼리를 줄일 수 있으며 성능향상을 기대해 볼 수 있다.
Prefetch(lookup, queryset=None, to_attr=None)
Prefetch의 원형이다.
우리는 추가적인 쿼리셋을 제어하고 싶을 때가 많다.
그럴 때 prefetch_related안에서 Prefetch를 사용하면 된다.
class Subquery(queryset, output_field=None)
Post.objects.annotate(newest_commenter_email=Subquery(newest.values('email')[:1]))
Comment.objects.filter(post__in=Subquery(posts.values('pk')))
comments = Comment.objects.filter(post=OuterRef('pk')).order_by().values('post')
total_comments = comments.annotate(total=Sum('length')).values('total')
Post.objects.filter(length__gt=Subquery(total_comments))
aggretate를 통해 값들을 만들어낸 후 annotate을 통해 이름을 부여해준다.
예시들
Company.objects.annotate(num_products=Count('products'))
Company.objects.annotate(num_products=Count(F('products')))
Company.objects.annotate(num_offerings=Count(F('products') + F('services')))