为什么这个Django(1.6)注释计数如此之慢?

时间:2015-03-22 14:01:39

标签: python django postgresql

总结:在计算相关对象时,我使用少量查询和注释与每个项目额外的两个查询进行非常慢的查询。数据库是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))

然而,这使我的查询非常慢!我在DB中有大约650条记录,django-debug-toolbar表示它进行了2次查询,平均大约400-500ms。我已经尝试过prefetch_related,但它不会让它更快。

我确实尝试了另一件事,在Collection模型中,我添加了这个:

@property
def have_count(self):
    return self.have.count()

@property
def want_count(self):
    return self.want.count()

并从我的视图中删除了注释。相反,它对数据库总共进行了42次查询,但是在20-25ms内完成。

我的注释在这里做错了什么?在一个查询中进行计数,与进行多次计数查询相比,不应该更快吗?

1 个答案:

答案 0 :(得分:0)

为什么变慢:如果您仅使用两个字段“ ManyToMany”中的注释,则会为所有这些表创建不必要的大联接一起。必须求值的行的笛卡尔积的大小大约为Have.objects.count() * Want.objects.count()。然后,您写了distinct=True来最终限制重复项的数量,以免获得无效的巨大结果。

修复旧版Django:如果仅使用queryset.annotate(have_count=Count("have")),则无需distinct=True即可快速获得正确的结果,或者使用distinct可以快速获得相同的结果。然后,您可以在内存中通过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查询,如上所述。