Django:替代使用 annotate(Count()) 来提高速度

Posted

技术标签:

【中文标题】Django:替代使用 annotate(Count()) 来提高速度【英文标题】:Django: alternative to using annotate(Count()) for speed 【发布时间】:2016-05-11 10:41:58 【问题描述】:

有两个模型具有一对多关系,A->B。在使用过滤器()后,我正在计算有多少条 A 的记录与相同的 B 相同。然后我需要根据连接到它们的最多 B 条记录来提取 A 的前 X 条记录。

当前代码:

class A(models.Model):
    code = models.IntegerField()
    ...

class B(models.Model):
    a = models.ForeignKey(A)
    ...

data = B.objects.all().filter(...)

top = data.values('a',...).annotate(n=Count('a')).distinct().order_by('-n')[:X];

我有大约 300k B 记录,使用我的笔记本电脑,一次查询需要大约 2 秒。我将查询分解为多个部分并对其进行计时,似乎主要瓶颈是 annotate()。

有什么方法可以用 Django 更快地做到这一点?

【问题讨论】:

【参考方案1】:

您应该在查询集中的annotate 之前添加.select_related('a')。这将强制 django 在计算模型之前加入模型。

https://docs.djangoproject.com/en/1.9/ref/models/querysets/#select-related

【讨论】:

如果我在 values() 调用之前这样做(之后不可能),时间保持不变。还是您的意思是我需要在 filter() 之前执行此操作,然后一组调用会更快? 你试过用print(data.sql)查看SQL吗? 我不确定您是否想要在这个实例中加入 A 模型:我们没有使用 A 模型中的任何东西,除了它的主键,它已经在B 表。【参考方案2】:

我怀疑减速实际上是在DISTINCT,而不是计数。

django 在使用queryset.values(x).annotate(...) 时构建查询的方式告诉它按第一个值分组,然后执行聚合。

B.objects.filter(...).values('a').annotate(n=Count('*')).order_by('-n')[:10]

这应该会生成如下所示的 SQL:

SELECT b.a, 
       count(*) AS n
  FROM b
 GROUP BY (b.a)
 ORDER BY count(*) DESC
 LIMIT 10

【讨论】:

以上是关于Django:替代使用 annotate(Count()) 来提高速度的主要内容,如果未能解决你的问题,请参考以下文章

使用 annotate() 包含一个 django 模型对象属性

Django:仅使用 annotate() 和 values() 计算非空 CharField

072:Django数据库ORM聚合函数详解-aggregate和annotate

Django基础aggregate和annotate方法使用详解与示例

Django annotate() 多次导致错误答案

使用 annotate Exists 时提高 Django 查询集性能