Queryset은 정말 필요한 시점에 필요한 만큼만 호출되는 LazyLoading 이라는 특징을 가지고 있다. 해당 데이터가 지금 당장 필요하지 않으면 호출하지 않는다.
그렇다면 실제 언제 SQL문이 호출 될까?
실제로 필요한 순간 출력하거나 값을 저장하거나 참조할 때 해당값을 db에서 loading 한다.
Queryset은 iterable하고, 반복문에서 queryset의 첫 row가 순회될 때, database에 호출이 일어난다 그리고 결과가 print 되기 전에 cache에 저장된다.
queryset = Entry.objects.all()
# evaluated and cached
for each in queryset:
print(each.headline)
# cache된 데이터를 사용한다.
for each in queryset:
print(each.headline)
evaluated 되지 않은 queryset을 Slicing하면 새로운 queryset을 반환한다. 반환된 queryset은 추가적인 수정(filter or ordering)을 허용하지 않는다. 다만 slicing을 더할 수는 있다.
# You can't use filter to queryset anymore.
queryset = Entry.objects.all()[10:100]
# You can use filter to q1 but not to q2, q3.
q1 = Entry.objects.all()
q2 = q1[1:10]
q3 = q2[1:5]
# saves results to cache of q1
lst1 = [each.blog.id for each in q1]
# saves results to cache of q2
lst2 = [each.blog.id for each in q2]
만약 벌써 evaluated된 queryset을 slicing한다면 queryset이 아닌 list가 반환 된다. evaluation 후에는 queryset은 cache 값을 사용하기 때문이다.
queryset = Entry.objects.all()
lst = list(queryset)
# returns a list of entry objects
first_ten = queryset[:10]
# list slicing not queryset slicing because first_ten is a list.
first_five = first_ten[:5]
queryset = Entry.objects.all()
# Queries the database because queryset hasn't been evaluated yet.
print(queryset[5])
lst = list(queryset)
# Using cache because evaluation happened in previous list() operation.
print(queryset[5])
print(queryset[10])
예외가 있는데, 만약 python slice 문법 중
step
parameter를 evaluated 되지 않은 Queryset에 사용하면 그 경우, 즉시 database로 query가 가고 queryset이 아닌 list가 반환 된다.
entry_list = Entry.objects.all()[1:100:2]
repr() method는 객체의 반환한다
repr()
method를 호출하여 Queryset이 evaluated 되었다면, 그 결과는 cache 되지 않느다.
# repr() evaluates but does not saves results to cache.
queryset = Entry.objects.all()
str_repr = repr(queryset)
# Not using cache.Hitting database again.
for each in queryset:
print(each.headline)
Note :
len
을 불러 Queryset이 평가되면
결과는 evaluated된 결과는 cache에 저장된다
# len() evaluates and saves results to cache.
queryset = Entry.objects.all() ln = len(queryset)
#Using cache from previous evaluation.
for each in queryset:
print(each.headline)
list
를 사용하여 evaluated된 queryset은 list
를 반환하고 그 결과를 cache에 저장한다.
# Evaluates the queryset and saves results in cache.
queryset = Entry.objects.all()
lst = list(queryset)
# Using cache from previous list() evaluation.
for each in queryset:
print(each.headline)
from django.db import models
class Person(models.Model):
name = models.CharField(max_length = 20)
age = models.IntegerField()
class Dog(models.Model):
name = models.CharField(max_length = 20)
age = models.IntegerField()
owner = models.ForeignKey('Person', on_delete = models.CASCADE)
위의 모델이 있을 때, 다음이 작성했다고 하자
dogs = Dog.objects.all()
for dog in dogs:
dog.owner
모든 dog들을 조회하기 위해 SQL이 1번 호출되고, for 문안에서 매번 owner인 Person의 정보를 매번 조회하기 위해 SQL문이 N번 호출 된다.
모든 Dog의 정보를 한 번에 가져 왔더라도, owner의 정보는 가져오지 않았으므로 이를 가져오기 위한 SQL문이 dog이 바뀔 때 마다 N번 호출 되는 것이다.
Eager loading(즉시 로딩)은 lazy loading(지연 로딩)과 반대되는 개념으로 즉시 로딩 시키는 방식이다.
두 가지 방식으로 eager loading 한다
select_related
: ForeignKey
, OnetoOneField
관계인 모델들을 함께 가져온다 (1:1, 1:N 관계에서 1 or N이 사용할 수 있다 역참조 불가)
SQL문의 JOIN을 이용해 연결된 모델을 같이 불러온다.
prefetch_related
: ForeignKey
, OnetoOneField
관계 뿐만 아니라 ManytoManyField
에서 사용가능 하다 (M : N 관계에서 1:N에서 1이 사용할 수 있다)