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