Делает ли `.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. Но здесь мы работаем с менеджером, причем с префетным менеджером. Это позволит не создавать новый запрос

Вернуться на верх