Does `.all()` in Django re-query the Database or use the cache?
I am getting very conflicting messages. From Django Queryset docs, I am reading (https://docs.djangoproject.com/en/5.1/ref/models/querysets/#django.db.models.query.QuerySet.all):
When a QuerySet is evaluated, it typically caches its results. If the data in the database might have changed since a QuerySet was evaluated, you can get updated results for the same query by calling all() on a previously evaluated QuerySet.
But then under the prefetch_related
section, it shows that using .all()
on the sub-objects uses results from the cache:
>>> restaurants = Restaurant.objects.prefetch_related(
... Prefetch("pizzas", queryset=queryset),
... )
>>> vegetarian_pizzas = restaurants[0].pizzas.all()
Here, apparently vegetarian_pizzas
doesn't trigger a database query.
So...which is it - what does all()
actually do? Why does it trigger a database query on the outer but then not for sub-objects? I'm confused.
Here, apparently vegetarian_pizzas doesn't trigger a database query.
QuerySet
s itself don't trigger queries. QuerySet
s are lazy, this means that only if you enumerate, call str(…)
or len(…)
on it, or anything else that actually needs the data it will make a query.
A QuerySet
also caches the result once a query is made, such that enumerate over it a second time, will not query the database a second time.
Now .all()
[Django-doc] makes a clone of the QuerySet
, and that clone thus has a different cache, this thus means that if you call .all()
and then you evaluate the original queryset, it will cache the results in the original queryset, but not in its .all()
clone. So:
qs1 = my_queryset
qs2 = qs1.all()
print(qs1)
print(qs2)
will make two queries.
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()
So to summarize, .all()
makes a clone if we are working with a QuerySet
. But here we work with a manager, and a prefetched manager. This will not make a new query no.