[Django] select_related() 와 prefetch_related() 란?

Yeshin Lee
6 min readApr 26, 2022

--

최근에 보았던 기술면접에서 나온 질문입니다. 정참조와 역참조의 개념만 애매하게 알고 있었는데, 영어 단어로 물어보셔서 더욱 당황했던 기억이 있습니다. 비록 사용해본 적은 없지만 미리 공부해보고자 합니다.

Django에서는 새 QuerySet을 반환하는 여러 Method를 제공하는데, select_relatedprefetch_related가 여기에 해당합니다. 두 함수 모두 데이터베이스 query의 수를 줄이면서 프로그램을 훨씬 빠르게 만들어 줍니다. 좀 더 알아보기 전에, QuerySetORM에 대해 간단하게 알아봅시다.

ORM이 무엇인지 명쾌하게 설명해줍니다.

ORM(Object-Relational Mapper)은 객체(Object)와 관계형(Relational) DB의 데이터를 매핑(Mapping)해주는 것을 의미합니다. SQL를 사용하지 않고 데이터 및 관계형 데이터베이스 작업을 훨씬 쉽게 만들어줍니다. 노마드코더강의에서는 다음과 같이 설명합니다.

1. You write Python
2. Django translates
3. The DB gets SQL
  • QuerySet데이터베이스의 객체 모음을 의미합니다. 필터를 안 가질 수도 혹은 여러 개를 가질 수 있는데, 필터는 주어진 매개변수를 기반으로 query 결과 범위를 좁힙니다.

select_related(정참조)는 foreign-key 관계를 가지는 QuerySet을 반환하고, query를 실행할 때 추가적으로 관련된 객체 데이터를 선택합니다. 하나의 더 복잡한 query를 만들지만, 후에 외래키를 사용할 때 DB query를 요구하지 않습니다. 일대일 관계(OneToOneField) 혹은 외래 키(ForeignKey) 객체를 가져올 때 사용합니다.

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)
# author와 hometown 테이블을 join해서 데이터베이스를 검색합니다.
b = Book.objects.select_related('author__hometown').get(id=4)
# b 는 관련 Person과 관련 City를 캐싱하기 때문에, 다음처럼 검색할 수 없습니다.
p = b.author # 데이터베이스를 검색할 수 없습니다.
c = p.hometown # 데이터베이스를 검색할 수 없습니다.

# select_related()를 사용하지 않는다면...
b = Book.objects.get(id=4) # Book 데이터베이스를 검색합니다.
p = b.author # author 데이터베이스를 검색합니다.
c = p.hometown # hometown 데이터베이스를 검색합니다.

prefetch_related(역참조)는 각 관계에 대해 별도의 조회를 수행하고 Python에서 “결합(joining)” 합니다. 외래 키 및 일대일 관계뿐만 아니라 다대다(ManyTOManyField)다대일(ManyToOneField) 객체를 미리 가져올 수 있습니다.

from django.db import models

class Topping(models.Model):
name = models.CharField(max_length=30)

class Pizza(models.Model):
name = models.CharField(max_length=50)
toppings = models.ManyToManyField(Topping)
def __str__(self):
return "%s (%s)" % (
self.name,
", ".join(topping.name for topping in self.toppings.all()),
)
>>> Pizza.objects.all()
["Hawaiian (ham, pineapple)", "Seafood (prawns, smoked salmon)"...
# 각 Pizza의 self.toppings.all()을 불러옵니다.
>>> Pizza.objects.all().prefetch_related('toppings')

--

--

Yeshin Lee
Yeshin Lee

No responses yet