Делает ли `.all()` в Django повторный запрос к базе данных или использует кэш?
Я получаю очень противоречивые сообщения. Из документации Django Queryset я читаю (https://docs.djangoproject.com/en/5.1/ref/models/querysets/#django.db.models.query.QuerySet.all):
Когда QuerySet оценивается, он обычно кэширует свои результаты. Если данные в базе данных могли измениться с момента оценки QuerySet, вы можете получить обновленные результаты для того же запроса, вызвав all() для ранее оцененного QuerySet.
Но затем в разделе prefetch_related
показано, что использование .all()
на под-объектах использует результаты из кэша:
>>> restaurants = Restaurant.objects.prefetch_related(
... Prefetch("pizzas", queryset=queryset),
... )
>>> vegetarian_pizzas = restaurants[0].pizzas.all()
Здесь, очевидно, vegetarian_pizzas
не вызывает запрос к базе данных.
Итак... что же это такое - что на самом деле делает all()
? Почему он запускает запрос к базе данных для внешнего объекта, но не для вложенных объектов? Я запутался.
Здесь, очевидно, vegetarian_pizzas не вызывает запрос к базе данных.
QuerySet
сами по себе не вызывают запросов. QuerySet
ленивы, это означает, что только если вы перечислите, вызовете str(…)
или len(…)
на нем, или что-то еще, что действительно нуждается в данных, он сделает запрос.
А QuerySet
также кэширует результат после выполнения запроса, так что перечисление по нему во второй раз не будет запрашивать базу данных во второй раз.
Теперь .all()
[Django-doc] создает клон QuerySet
, и этот клон, таким образом, имеет другой кэш, Таким образом, это означает, что если вы вызовете .all()
и затем оцените исходный набор запросов, он будет кэшировать результаты в исходном наборе запросов, но не в его .all()
клоне. Итак:
qs1 = my_queryset
qs2 = qs1.all()
print(qs1)
print(qs2)
сделает два запроса.
The confusion probably stems from the fact that .all()
also has an extra use-case when you are working with a manager. You can not enumerate over a manager, like my_restaurant.pizzas
, it thus is used as a tool to get the underlying QuerySet
by using .all()
. Indeed, if we look at the source code [GitHub]:
def all(self): # We can't proxy this method through the `QuerySet` like we do for the # rest of the `QuerySet` methods. This is because `QuerySet.all()` # works by creating a "copy" of the current queryset and in making said # copy, all the cached `prefetch_related` lookups are lost. See the # implementation of `RelatedManager.get_queryset()` for a better # understanding of how this comes into play. return self.get_queryset()
Подводя итог, можно сказать, что .all()
создает клон, если мы работаем с QuerySet
. Но здесь мы работаем с менеджером, причем с префетным менеджером. Это позволит не создавать новый запрос