总结:在计算相关对象时,我使用很少的查询和注释得到非常慢的查询,而每项额外的两个查询。数据库是 PostgreSQL 9.3.5。
我有一个看起来像这样的模型:
class Collection(models.Model):
have = models.ManyToManyField(Item, related_name='item_have', through='Have')
want = models.ManyToManyField(Item, related_name='item_want', through='Want')
added = models.DateTimeField()
class Meta:
ordering = ['-last_bump']
class Have(models.Model):
item = models.ForeignKey(Item)
collection = models.ForeignKey(Collection, related_name='have_set')
price = models.IntegerField(default=0)
class Want(models.Model):
want = models.ForeignKey(Item)
collection = models.ForeignKey(Collection, related_name='want_set')
price = models.IntegerField(default=0)
在我看来,我列出了这些集合,我想显示每个集合中有多少想要和有多少,通过注释来实现:
class ListView(generic.ListView):
model = Collection
queryset = Collection.objects.select_related()
paginate_by = 20
def get_queryset(self):
queryset = super(ListView, self).get_queryset()
queryset = queryset.annotate(have_count=Count("have", distinct=True),
want_count=Count("want", distinct=True))
但是,这使我的查询非常慢!我在数据库中有大约 650 条记录,django-debug-toolbar 说它进行了 2 次查询,平均耗时 400-500 毫秒左右。我试过使用 prefetch_related,但它并没有使它变得更快。
我确实尝试了另一件事,在 Collection 模型中,我添加了这个:
@property
def have_count(self):
return self.have.count()
@property
def want_count(self):
return self.want.count()
并从我的 View 中删除了注释。相反,它对数据库进行了总共 42 次查询,但在 20-25 毫秒内完成。
我在这里的注释做错了什么?在一个查询中执行计数是否应该比执行多个计数查询更快?
最佳答案
为什么慢:如果您只是使用两个 ManyToMany 字段的注释,那么您会创建一个不需要的所有这些表的大连接一起。必须计算的行的笛卡尔积的大小大约为 Have.objects.count() * Want.objects.count()
。然后你写了 distinct=True
来最终限制重复项的数量,以免得到无效的巨大结果。
修复旧的 Django:如果您只使用 queryset.annotate(have_count=Count("have"))
,您将在没有 distinct=True
的情况下快速获得正确的结果> 或相同的结果也很快与不同。然后你可以在内存中通过 Python 合并两个查询的结果。
解决方案 在 Django >= 1.11(在您提出问题两年后)中可以使用一个查询两个>子查询,一个用于Have
,一个用于Want
,都是一个请求,但不要将所有表混合在一起。
from django.db.models import Count, OuterRef, Subquery
sq = Collection.objects.filter(pk=OuterRef('pk')).order_by()
have_count_subq = sq.values('have').annotate(have_count=Count('have')).values('have_count')
want_count_subq = sq.values('want').annotate(have_count=Count('want')).values('want_count')
queryset = queryset.annotate(have_count=Subquery(have_count_subq),
want_count=Subquery(want_count_subq))
验证:您可以通过打印 str(my_queryset.query)
来检查慢速和固定 SQL 查询是否如上所述。
关于python - 为什么这个 Django (1.6) 注释计数这么慢?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29195299/