You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Django中Annotate与Subquery计数远慢于循环查询的优化求助

Django多表关联统计查询优化方案

核心问题拆解

你遇到的性能差异根源在于:annotate/Subquery会生成跨Project、Batch、Document、Registered的多表JOIN语句,面对百万级的Document数据,JOIN后临时表的计算、排序、去重开销极大;而循环查询每次仅针对单个Project ID过滤,查询范围精准,数据库能高效利用索引完成单条查询,多次小查询的总耗时反而低于一次大JOIN。

具体优化方案

1. 给Document表新增Project外键(最优解)

直接在Document表添加project外键字段,同步历史数据后,跳过Batch表的关联,直接通过Document与Project的关联统计:

# 新增外键后的查询写法
projects = Project.objects.filter(completion_date__isnull=True).annotate(
    count=Count('documents', filter=Q(documents__registered__isnull=False), distinct=True)
)

这会减少一次JOIN操作,数据库查询逻辑大幅简化,性能能接近循环查询的水平。

2. 给现有关联字段加联合索引(无需改表结构的折中方案)

如果暂时无法修改表结构,给以下字段添加联合索引:

  • Batch表:(project_id, id)
  • Document表:(batch_id, registered_id)(包含registered_id可让索引覆盖过滤条件,避免回表)
  • Registered表:(document_id)
    索引能让数据库快速定位目标数据,减少全表扫描次数,大幅提升annotate/Subquery的查询效率。

3. 精简Subquery写法

你当前的Subquery存在冗余,可简化为:

from django.db.models import Subquery, Count

subq_registered = Document.objects.filter(
    batch__project_id=OuterRef('id'), 
    registered__isnull=False
).values('batch__project_id').annotate(count=Count('id')).values('count')

projects = projects.annotate(count=Subquery(subq_registered))

这种写法会生成更简洁的SQL,减少不必要的计算步骤。

4. 预统计缓存(非实时场景适用)

如果统计数据不需要实时更新,可新增registered_doc_count字段到Project表,定时(如每小时)通过脚本预计算并更新该字段,查询时直接读取字段值,性能可达毫秒级。

内容的提问来源于stack exchange,提问作者gistix

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 11:05:09