Django中annotate多关联计数查询过慢,求优化方法
优化Django多关联Count Annotate查询的性能
问题根源
原annotate查询慢的核心原因是多表关联导致的笛卡尔积爆炸。当你同时对多个反向关联使用Count(distinct=True)时,Django ORM会将所有关联表与主表做JOIN,生成的中间结果集是各关联表行数的乘积。哪怕每个关联表只有少量数据,多个关联叠加后,中间结果集的规模会急剧膨胀,数据库需要对这个巨大的数据集去重计数,自然耗时极长。
优化方案:使用Subquery替代多表Count
通过Subquery和OuterRef,为每个关联表单独编写计数子查询,避免多表JOIN产生的笛卡尔积。每个子查询独立统计对应关联表的数量,主查询仅将这些子查询的结果关联到主表,整体只产生1个查询,同时性能大幅提升。
代码实现
from django.db.models import Subquery, OuterRef, Count from django.db import models # 为每个关联模型定义子查询(以Character为例) character_count = Character.objects.filter( campaign=OuterRef('pk') ).values('campaign').annotate( count=Count('id') ).values('count') faction_count = Faction.objects.filter( campaign=OuterRef('pk') ).values('campaign').annotate( count=Count('id') ).values('count') location_count = Location.objects.filter( campaign=OuterRef('pk') ).values('campaign').annotate( count=Count('id') ).values('count') quest_count = Quest.objects.filter( campaign=OuterRef('pk') ).values('campaign').annotate( count=Count('id') ).values('count') loot_count = Loot.objects.filter( campaign=OuterRef('pk') ).values('campaign').annotate( count=Count('id') ).values('count') entry_count = Entry.objects.filter( campaign=OuterRef('pk') ).values('campaign').annotate( count=Count('id') ).values('count') # 执行主查询并annotate结果 campaigns = Campaign.objects.annotate( num_characters=Subquery(character_count, output_field=models.IntegerField()), num_factions=Subquery(faction_count, output_field=models.IntegerField()), num_locations=Subquery(location_count, output_field=models.IntegerField()), num_quests=Subquery(quest_count, output_field=models.IntegerField()), num_loot=Subquery(loot_count, output_field=models.IntegerField()), num_entries=Subquery(entry_count, output_field=models.IntegerField()), )
代码说明
OuterRef('pk'):引用主查询中Campaign的主键,让子查询针对每个Campaign单独统计。values('campaign').annotate(count=Count('id')):按campaign分组,统计每个分组下的关联对象数量,确保每个子查询仅返回单个数值。Subquery(...):将子查询的结果作为主查询的一个字段返回,避免多表JOIN。
方案优势
- 性能显著提升:避免了笛卡尔积,数据库无需处理巨大的中间结果集,查询速度接近循环查询,但不会产生N+1问题。
- 仅1次数据库查询:所有统计逻辑都在单个SQL查询中完成,避免了循环带来的大量数据库请求。
- 符合Django ORM规范:无需编写原生SQL,保持代码的可维护性。
内容的提问来源于stack exchange,提问作者Kevin Renskers
相关产品推荐
相关产品推荐

