DB를 자주 호출한다면 즉, Query를 많이 한다면 통신은 느려질 수밖에 없습니다.
그렇기 때문에, 줄일 수 있는 Query는 줄이는게 좋습니다.
그리고 Django에는 record(혹은 instance)간의 관계를 미리 읽어들여서
Query를 줄이는 ORM이 존재합니다.
다음과 같은 모델을 예로 들겠습니다.
class City(models.Model):
name = models.CharField(max_length=20)
class Person(models.Model):
city = models.ForeignKey(City, on_delete=models.CASCADE)
name = models.CharField(max_length=20)
class Pet(models.Model):
person = models.ForeignKey(Person, on_delete=models.CASCADE)
name = models.CharField(max_length=20)
# Hits the database.
zunky = Person.objects.get(id=1)
# Hits the database again to get the related City object.
seoul = zunky.city
위의 경우, Person의 instance를 한번 받아오고(zunky)
그 instance가 참조하고 있는 instance(seoul)를 한번 더 받아옵니다.
Django의 ORM을 이용해 이 Query를 줄일 수 있습니다.
# Hits the database.
zunky = Person.objects.select_related('city').get(id=1)
# Doesn't hit the database, because e.blog has been prepopulated
# in the previous query.
seoul = zunky.city
zunky라는 instace를 불러올 때, select_related()라는 QuerySet API를 이용하면
instance가 참조하고 있는 대상을 caching합니다.
그러므로, zunky.city를 읽을 때,
다시 DB로 갈 필요없이 caching되어 있는 값을 읽어오므로
DB 호출을 줄일 수 있습니다.
select_related()는 QuerySet에 적용할 수 있습니다.
for person in Person.objects.filter(city_id=2).select_related('city'):
# Without select_related(), this would make a database query for each
# loop iteration in order to fetch the related city for each person.
print(person.city.name)
filter()와 select_related() 순서는 중요하지 않습니다.
다음 QuerySet은 같습니다.
Person.objects.filter(city_id=2).select_related('city')
Person.objects.select_related('city').filter(city_id=2)
ForeignKey를 더 파고들어 갈 수 있습니다.
class City(models.Model):
name = models.CharField(max_length=20)
class Person(models.Model):
city = models.ForeignKey(City, on_delete=models.CASCADE)
name = models.CharField(max_length=20)
class Pet(models.Model):
person = models.ForeignKey(Person, on_delete=models.CASCADE)
name = models.CharField(max_length=20)
# Hits the database with joins to the author and hometown tables.
choon_sik = Pet.objects.select_related('person__city').get(id=1)
zunky = choon_sik.person # Doesn't hit the database.
seoul = zunky.city # Doesn't hit the database.
# Without select_related()...
choon_sik = Pet.objects.get(id=1) # Hits the database.
zunky = choon_sik.person # Hits the database.
seoul = zunky.city # Hits the database.
여러 Field를 가져오는 것도 가능합니다.
select_related('foo', 'bar')
select_related('foo').select_related('bar')
그리고 위 두 방법은 같은 결과를 초래합니다.
select_related 은 SQL의 JOIN을 사용하는 특성상 foreign-key , one-to-one 와 같은 single-valued relationships에서만 사용이 가능하다는 한계가 있습니다.
위의 select_related()를 사용할 때
Person의 객체인 zunky가 갖고 있는 city는 seoul하나였습니다.
하나의 Field가 ManyToMany 관계라면 어떨까요?
다음과 같은 모델이 있다고 가정하겠습니다.
class City(models.Model):
name = models.CharField(max_length=20)
class Language(models.Model):
name = models.CharField(max_length=100)
class Person(models.Model):
city = models.ForeignKey(City, on_delete=models.CASCADE)
name = models.CharField(max_length=20)
language = models.ManyToManyField(Language, through='JoinPersonLanguage')
class JoinPersonLanguage(models.Model):
person = models.ForeignKey(Person, on_delete=models.CASCADE)
language = models.ForeignKey(Language, on_delete=models.CASCADE)
위의 모델을 보면 Person의 한 객체가 여러 Language instance를 참조할 수 있습니다.
예를 들면, 다음과 같습니다.
zunky: Python, JavaScript, C
이제 Many-To-Many 관계일 때, 효과적으로 DB를 사용하는 방법에 대해 알아보겠습니다.
zunky = Person.objects.prefetch_related('language').get(id=1)
zunky.language.values() # Doesn't hit the database.
<QuerySet [{'id': 1, 'name': 'Python'}, {'id': 2, 'name': 'C'}, {'id': 4, 'name': 'Javascript'}]>
zunky = Person.objects.get(id=1)
zunky.language.values() # Hits the database.
<QuerySet [{'id': 1, 'name': 'Python'}, {'id': 2, 'name': 'C'}, {'id': 4, 'name': 'Javascript'}]>
Many-to-Many 관계인 경우에는
위와 같이 prefetch_related()를 사용합니다.
여태까지는 정방향 참조를 다루었습니다.
Person table에는 "city_id", "language_id" Coulumn이 있기 때문에
정방향 참조가 가능했습니다.
그런데, City를 참조하는 Person을 알고 싶다면 어떻게 하면 될까요?
다음과 같은 모델이 있다고 가정하겠습니다.
class City(models.Model):
name = models.CharField(max_length=20)
class Language(models.Model):
name = models.CharField(max_length=100)
class Person(models.Model):
city = models.ForeignKey(City, on_delete=models.CASCADE)
name = models.CharField(max_length=20)
language = models.ManyToManyField(Language, through='JoinPersonLanguage')
class JoinPersonLanguage(models.Model):
person = models.ForeignKey(Person, on_delete=models.CASCADE)
language = models.ForeignKey(Language, on_delete=models.CASCADE)
City에는 seoul이라는 row가 있습니다.
그리고 seoul을 참조하고 있는 Person의 객체들이 있다고 한다면
다음과 같이 값들을 호출할 수 있습니다.
>>> seoul = City.objects.get(id=1)
>>> seoul.person_set.values()
<QuerySet [{'id': 1, 'city_id': 1, 'name': 'zunky'}, {'id': 4, 'city_id': 1, 'name': 'byeong-min'}, {'id': 5, 'city_id': 1, 'name': 'sae-geul'}]>
현재는 seoul이라는 City의 객체하나뿐이지만,
만약, 모든 City 객체들에 역참조를 해야하는 상황이라면
person_set이라는 Query를 City의 수마다 DB에 요청하게됩니다.
cities = City.objects.all()
for city in cities:
print(city.person_set.values())
위와 같이 명령을 실행한다면
City의 객체수 만큼 Query를 DB에 요청하게 되는 것입니다.
City의 객체가 seoul, incheon, busan이라면 Query는 3번 요청하게 됩니다.
이런 상황일 때, prefetch_related()를 사용하면 Query를 줄일 수 있습니다.
cities = City.objects.prefetch_related('person_set').all()
for city in cities:
print(city.person_set.values())
_set
을 이용한 뒤, 원하는 Query를 실행한다.