Django在访问后会缓存相关的ForeignKey和ManyToManyField字段吗?
假设有一个这样的模型,Django在第一次访问相关对象后,会把这些对象缓存起来吗?
class Post(models.Model):
authors = models.ManyToManyField(User)
category = models.ForeignKey(Category)
举个例子:
post = Post.objects.get(id=1)
# as i understand this hits the database
authors1 = post.authors.all()
# does this his the database again?
authors2 = post.authors.all()
# as i understand this hits the database
category1 = post.category
# does this hit the database again?
category2 = post.category
注意:目前我在使用Django 1.3,但了解其他版本的情况也是很有帮助的。
2 个回答
0
Django在第一次访问ForeignKey
字段后会把它缓存起来,这样下次再用的时候就不用再去数据库查了。不过,ManyToMany
字段就不会被缓存,正如Daniel Roseman在其他回答的评论中提到的。这一点在Django的文档中也有提到,关于数据库优化的部分(虽然没有特别提到ManyToMany)
不过一般来说,可调用的属性每次都会导致数据库查询:
我通过以下测试确认了这一点,测试是在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)
9
在第一个例子中,第二个查询是被缓存的。在第二种情况下(我认为)如果不使用 select_related
,两个查询都会访问数据库:
post = Post.objects.select_related('category').get(id=1)
编辑
我之前关于第二个例子的理解是错的。如果在原始查询中使用了 select_related
,那么就不会再访问数据库了(外键会立即被缓存)。如果不使用 select_related
,第一次查询会访问数据库,但第二次查询会被缓存。
来源:
https://docs.djangoproject.com/en/dev/topics/db/queries/#one-to-many-relationships
第一次访问一对多关系时,相关对象的访问会被缓存。之后对同一个对象实例的外键的访问也会被缓存。
需要注意的是,
select_related()
这个查询集方法会提前递归地填充所有一对多关系的缓存。