python - 为什么这个 Django (1.6) 注释计数这么慢?

标签 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))

但是,这使我的查询非常慢!我在数据库中有大约 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/

相关文章:

javascript - 如果多个用户同时调用 Python 函数是否有效?

django - django.contrib.auth.login 和 django.contrib.auth.views.login 之间的区别

django - 扩展 Django 1.11 用户模型

postgresql - 如何使用 ssl 选项保护 npgsql 连接

python - 如何将一个 Sphinx 角色转换为另一个角色?

python - 如何阅读行中的特定单词?

javascript - 我们总是可以使用 knex 和 postgres 将日期列作为字符串 (varchar) 获取吗?

ruby-on-rails - 计算 rails update 中 created_at 和 now() 之间的差异

python - 根据负载值对端口号进行排序

django - 使用自定义 AdminSite 时我的应用程序不可见