【问题标题】:Why is this Django (1.6) annotate count so slow?为什么这个 Django (1.6) 注释计数这么慢?
【发布时间】:2015-05-25 13:27:13
【问题描述】:

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

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

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

【问题讨论】:

  • 一个建议,在询问有关调试慢查询的问题时,使用调试工具栏获取正在运行的 sql 查询并为它们运行 EXPLAIN 查询。并在您的问题中发布这些 sql 查询和 EXPLAIN,这样其他人会更容易回答。
  • "在一个查询中进行计数是否比进行多次计数查询更快?"为什么会这样? SQL 是一种有趣的动物。我怀疑您正在生成巨大的笛卡尔积,然后将它们压缩到内存中的计数中,而与 count() 一样,您只是在运行超快速的一次 select count(*) from table; 查询。
  • 我认为这是 django annotate + postgres 的组合。我现在遇到了同样的问题,我正在将所有注释“迁移”到 .count()。它工作得更快
  • 如果您真的在问“是什么阻碍了查询?”我认为独特的东西正在减慢它的速度。如果没有它,你就不会得到你想要的东西。您可以尝试使其与存储过程一起使用。

标签: python django postgresql


【解决方案1】:

为什么慢:如果您只是使用 两个 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 查询是否如上所述。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-10-20
    • 1970-01-01
    相关资源
    最近更新 更多