9👍
In the first example the second query is cached. In the second case (I believe) they will both cause a DB hit unless you use select_related
on the original query:
post = Post.objects.select_related('category').get(id=1)
EDIT
I’m wrong about the second example. If you use select_related
in the original query, you won’t hit the database at all again (The ForeignKey is cached immediately). If you don’t use select_related
, you will hit the DB on the first query but the second query will be cached.
From:
https://docs.djangoproject.com/en/dev/topics/db/queries/#one-to-many-relationships
Forward access to one-to-many relationships is cached the first time the related object is accessed. Subsequent accesses to the foreign key on the same object instance are cached.
Note that the select_related() QuerySet method recursively prepopulates the cache of all one-to-many relationships ahead of time.
0👍
Django seems to cache ForeignKey
fields after they are first accessed, but will not cache ManyToMany
, as mentioned in the comments to the other answer by Daniel Roseman. This is also mentioned in the Django docs about DB optimizations (although it doesn’t explicitly call out ManyToMany)
But in general, callable attributes cause DB lookups every time:
I confirmed this with the following test, executed against Django 3.2.8
from django.db.models import prefetch_related_objects
from django.test import TestCase
from django.db import connection
from django.test.utils import CaptureQueriesContext
from django.db import models
class Author(models.Model):
name = models.CharField(max_length=100)
class Category(models.Model):
category_name = models.CharField(max_length=100)
class Post(models.Model):
name = models.CharField(max_length=100)
authors = models.ManyToManyField(Author)
category = models.ForeignKey(Category, on_delete=models.CASCADE)
class TestQueryCache(TestCase):
def _create_objects(self):
author1 = Author(name="first author")
author1.save()
author2 = Author(name="second author")
author2.save()
category1 = Category(category_name="first category")
category1.save()
category2 = Category(category_name="second category")
category2.save()
post = Post.objects.create(name="test post", category=category1)
post.authors.add(author1, author2)
post.save()
return post
def test_foreign_key(self):
self._create_objects()
post = Post.objects.get(name="test post")
with CaptureQueriesContext(connection) as ctx:
category1 = post.category.category_name
category2 = post.category.category_name
print("ForeignKey queries are")
for query in ctx.captured_queries:
print(query)
# this call pattern makes a single query, since foreign keys are cached
# https://docs.djangoproject.com/en/dev/topics/db/queries/#forward
# "Forward access to one-to-many relationships is cached the first time the related object is accessed. Subsequent accesses to the foreign key on the same object instance are cached."
# SELECT "coaching_category"."id", "coaching_category"."category_name" FROM "coaching_category" WHERE "coaching_category"."id" = 1 LIMIT 21
def test_many_to_many(self):
self._create_objects()
post = Post.objects.get(name="test post")
with CaptureQueriesContext(connection) as ctx:
authors1 = [author.name for author in post.authors.all()]
authors2 = [author.name for author in post.authors.all()]
print("Without prefetching, ManyToMany queries are")
for query in ctx.captured_queries:
print(query)
# This call pattern makes two queries, it seems that unlike ForeignKey, ManyToMany relationships are not cached
# SELECT "coaching_author"."id", "coaching_author"."name" FROM "coaching_author" INNER JOIN "coaching_post_authors" ON ("coaching_author"."id" = "coaching_post_authors"."author_id") WHERE "coaching_post_authors"."post_id" = 2
# SELECT "coaching_author"."id", "coaching_author"."name" FROM "coaching_author" INNER JOIN "coaching_post_authors" ON ("coaching_author"."id" = "coaching_post_authors"."author_id") WHERE "coaching_post_authors"."post_id" = 2
def test_many_to_many_prefetching(self):
self._create_objects()
post = Post.objects.get(name="test post")
with CaptureQueriesContext(connection) as ctx:
prefetch_related_objects([post], "authors")
# as i understand this hits the database
authors1 = [author.name for author in post.authors.all()]
# does this his the database again?
authors2 = [author.name for author in post.authors.all()]
print("With prefetching, ManyToMany queries are")
for query in ctx.captured_queries:
print(query)
# using prefetch allows ManyToMany to be cached, this call pattern makes only a single query
# SELECT ("coaching_post_authors"."post_id") AS "_prefetch_related_val_post_id", "coaching_author"."id", "coaching_author"."name" FROM "coaching_author" INNER JOIN "coaching_post_authors" ON ("coaching_author"."id" = "coaching_post_authors"."author_id") WHERE "coaching_post_authors"."post_id" IN (3)
- Django Channels VS Django 3.0 / 3.1?
- For the django admin, how do I add a field to the User model and have it editable in the admin?
- Django select_related for multiple foreign keys
- How do I restrict access to admin pages in Django?
- Model in sub-directory via app_label?