QuerySet может быть сконструирован, отфильтрован, нарезан и, как правило, передан без обращения к базе данных. Фактически никаких действий с базой данных не происходит, пока вы не сделаете что-нибудь для оценки QuerySet.
В этой статье мы будем говорить о следующих моделях:
from django.db import models
class Blog(models.Model):
name = models.CharField(max_length=100)
tagline = models.TextField()
def __str__(self):
return self.name
class Entry(models.Model):
blog = models.ForeignKey(Blog, on_delete=models.CASCADE)
headline = models.CharField(max_length=255)
body_text = models.TextField(blank=True)
class Meta:
default_related_name = 'entries'
def __str__(self):
return self.headline
Взгляните на следующий пример, чтобы понять ленивые запросы:
q1 = Entry.objects.filter(blog=2)
q2 = q1.filter(headline__contains='food')
entry_list = list(q3)
Хотя это выглядит как два запроса в базу данных, на самом деле запрос в базу данных выполняется только один раз, в последней строке (entry_list = list (q3))
.
Каждый раз, когда вы уточняете QuerySet, вы получаете отдельный QuerySet, не связанный с предыдущим, который можно сохранять, использовать и повторно использовать.
В следующем коде q и q3 выполняют ту же операцию с базой данных. Обратите внимание, что q1, q2, q3 - это три различных QuerySet.
q = Entry.objects.filter(blog=2).exclude(body_text__icontains="food")
q1 = Entry.objects.filter(blog=2)
q2 = q1.exclude(body_text__icontains="food")
q3 = q2[:10]
Теперь поговорим о выполнении и кешировании.
Выполнение означает фактическое обращение к базе данных. В тот момент, когда вы начинаете перебирать QuerySet, все строки, соответствующие QuerySet, извлекаются из базы данных и конвертируются в модели Django. Эти модели затем сохраняются во встроенном кэше QuerySet, поэтому при повторении QuerySet вам не нужно снова обращаться к базе данных.
Чтобы включить кеширование в QuerySet, просто сохраните QuerySet в переменной и повторно используйте её. Класс Django QuerySet имеет переменную _result_cache
, в которой он сохраняет результаты запроса (модели Django) в списке. _result_cache
имеет значение None, если QuerySet не имеет кеша, в противном случае - список объектов модели. Когда вы перебираете кэшированный QuerySet, вы в основном перебираете _result_cache
, который является списком.
# Следующий код создаст два QuerySet, выполнит их и забудет,
# потому что они нигде не сохраняют набор запросов, чтобы использовать их позже.
print([e.headline for e in Entry.objects.all()])
print([e.pub_date for e in Entry.objects.all()])
# Следующий код сохраняет QuerySet в переменной.
# Когда он выполнится, он сохранит результаты в свой кеш (_result_cache).
queryset = Entry.objects.all()
# выполнение с итерацией.
for each in queryset:
print(each.headline)
# Использование кеша из предыдущей оценки.
for each in queryset:
print(each.id)
Итерация - не единственный способ оценки, есть много других способов оценки, давайте обсудим их с примерами.
QuerySet является итеративным, и до того, как вы начнете проходить свою первую строку, происходят обращения к базе данных, и результаты сохраняются в кеше. В следующем примере обращения к базе данных и кэширование происходит до печати первого заголовка:
queryset = Entry.objects.all()
# Выполнение и кэширование
for each in queryset:
print(each.headline)
# Использование кеша из предыдущей оценки.
for each in queryset:
print(each.headline)
Нарезка невычисленного QuerySet возвращает новый QuerySet. Возвращенный QuerySet не допускает дальнейшие модификации (например, добавление дополнительных фильтров или изменение порядка), но допускает большее количество срезов. Queryset (нарезанный или нет) сохраняет результаты в свой кеш, если вы выполняете итерацию по нему:
# Вы больше не можете использовать фильтр для набора запросов.
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]
# сохраняет результаты в кеш q1
lst1 = [each.blog.id for each in q1]
# сохраняет результаты в кеш q2
lst2 = [each.blog.id for each in q2]
Если вы разрезаете уже оцененный Queryset, он возвращает список объектов, а не объекты QuerySet, потому что после оценки, когда вы снова выполняете итерацию, QuerySet использует свое кэшированное (_result_cache
) значение, которое является списком.
queryset = Entry.objects.all()
lst = list(queryset)
# возвращает список входных объектов
first_ten = queryset[:10]
# нарезка списка, а не нарезка набора запросов, потому что first_ten - это список.
first_five = first_ten[:5]
Если вы используете индекс для выбора одного элемента из неоцененного QuerySet, он вызывает запрос в базу данных, но если вы выбираете из уже оцененного QuerySet, он использует кеш.
queryset = Entry.objects.all()
# Запрашивает базу данных, поскольку набор запросов еще не был оценен.
print(queryset[5])
# Запрашивает базу данных, поскольку набор запросов еще не был оценен.
print(queryset[5])
lst = list(queryset)
# Использование кешей, поскольку оценка выполнялась в предыдущем list().
print(queryset[5])
print(queryset[10])
Исключением является использование параметра step
синтаксиса фрагмента Python для неоцененного QuerySet. В этом случае он немедленно выполняет запрос и возвращает список объектов модели, а не объект QuerySet.
entry_list = Entry.objects.all()[1:100:2]
Если вы упаковываете QuerySet, он будет оценен.
Метод repr()
возвращает печатаемую строку представления данного объекта. QuerySet оценивается, когда вы вызываете для него repr()
, но он не сохраняет результаты в кеш.
# repr() оценивает, но не сохраняет результаты в кеш.
queryset = Entry.objects.all()
str_repr = repr(queryset)
# Не используется кеш. Повторный запрос в базу данных.
for each in queryset:
print(each.headline)
Примечание: функция print()
также оценивает QuerySet, но не сохраняет результаты в кеш.
QuerySet оценивается, когда вы вызываете len()
, и сохраняет оцененные результаты в кеш.
# len() оценивает и сохраняет результаты в кеш.
queryset = Entry.objects.all()
ln = len(queryset)
# Использование кеша из предыдущей оценки.
for each in queryset:
print(each.headline)
Примечание. Не используйте это, если вам нужно знать только количество элементов в QuerySet. По этой причине в Django есть функция count()
.
Принудительно оценивает QuerySet, вызвав для него list()
, который возвращает список объектов моделей и сохраняет результаты в кеше.
# Оценивает набор запросов и сохраняет результаты в кеше.
queryset = Entry.objects.all()
lst = list(queryset)
# Использование кеша из предыдущей оценки list().
for each in queryset:
print(each.headline)
Оператор if вызовет выполнение запроса и сохранит результаты в кеше. Если есть хотя бы один результат, QuerySet имеет значение True
, в противном случае - False
. Например:
# Оператор `if` оценивает набор запросов и сохраняет результаты в кеше.
queryset = Entry.objects.all()
if queryset:
# Использование кеша из предыдущей оценки оператора if.
for each in queryset:
print(each.headline)
Когда Django оценивает QuerySet, поля прямых или обратных отношений не включаются в запрос и, следовательно, не включаются в кеш, если вы не используете select_related
или prefetch_related
.
queryset = Entry.objects.all()
for each in queryset:
print(each.headline)
# запрос в БД
print(each.blog.name)
for each in queryset:
# использует кеш
print(each.headline)
# Нет кеша, снова запрос в базу данных для блога.
print(each.blog.name)
# Используйте select_related или prefetch_related для кеширования связанных объектов
queryset = Entry.objects.select_related('blog')
for each in queryset:
print(each.headline)
print(each.blog.name)
for each in queryset:
# использует кеш
print(each.headline)
# использует кеш
print(each.blog.name)