如何优化Django ORM中的循环查询 消除多余数据库查询请求
问题本质
你当前的代码属于典型的N+1查询问题:先执行1次查询拿到code列表,再遍历每个code分别查询对应Data数据,几百个code就会产生数百次数据库请求,性能开销极高。
优化方案
方案1:窗口函数实现(最优,仅需2次数据库查询)
利用SQL窗口函数直接在数据库层面完成每个code对应数据的排序和Top N筛选,性能最好,支持Django 2.0+版本。
from django.db.models import F, Window from django.db.models.functions import RowNumber from collections import defaultdict n = 3 # 批量拿到需要查询的目标ID列表 target_ids = Target.objects.filter(code__in=['ABC', 'CDE', ...]).values_list('id', flat=True) # 窗口函数给每个target下的Data按spec倒序排名,直接过滤出前n条 top_data = Data.objects.filter(target_id__in=target_ids).annotate( row_num = Window( expression=RowNumber(), partition_by=[F('target_id')], order_by=F('spec').desc() ) ).filter(row_num__lte=n).values('target__code', 'spec', 'spec_type') # 按code分组,得到和原代码结构一致的result result_map = defaultdict(list) for item in top_data: result_map[item['target__code']].append({ 'spec': item['spec'], 'spec_type': item['spec_type'] }) # 按原始code的顺序组装结果 result = [result_map[code.code] for code in Target.objects.filter(id__in=target_ids)]
方案2:全量拉取内存分组(兼容旧版数据库)
如果你的数据库不支持窗口函数(如MySQL 5.7及以下版本),可以一次性拉取所有符合条件的Data,在内存中分组取Top N,同样仅需2次数据库查询。
from collections import defaultdict n = 3 targets = Target.objects.filter(code__in=['ABC', 'CDE', ...]) # 一次性拉取所有关联的Data,提前按code和spec倒序排序 all_data = Data.objects.filter(target__in=targets).values( 'target__code', 'spec', 'spec_type' ).order_by('target__code', '-spec') # 内存分组取每个code的前n条 result_map = defaultdict(list) for item in all_data: code = item['target__code'] if len(result_map[code]) < n: result_map[code].append({ 'spec': item['spec'], 'spec_type': item['spec_type'] }) result = [result_map[target.code] for target in targets]
额外性能优化建议
给Data表添加(target_id, spec)联合索引,能避免查询时的文件排序,直接走索引拿到排序后的数据,查询速度会有明显提升,模型定义修改如下:
class Data(models.Model): target = models.ForeignKey(Target, on_delete=models.CASCADE) spec_type = models.CharField(max_length=255) spec = models.FloatField() class Meta: indexes = [ models.Index(fields=['target', 'spec']), ]
内容的提问来源于stack exchange,提问作者ellryn
相关产品推荐
相关产品推荐

