Django ORM filter查询时如何一次性获取关联model_set数据
Django 一对多关联查询避免N+1问题方案
核心实现思路
你当前的循环查询属于典型的ORM N+1性能问题,直接用Django内置的预加载能力即可解决,全程仅需2次数据库查询,不需要逐ID循环查库。
首先在查询Shipment时,调用prefetch_related方法传入关联字段的related_name值,框架会自动一次性拉取所有关联的ShipmentTimeline数据并完成关联匹配:
# 原查询逻辑追加prefetch_related即可 shipment_queryset = Shipment.objects.filter( # 此处保留你原有的过滤条件 ).only('id').prefetch_related("shipmenttimeline")
注意:此处不要用
select_related,该方法仅支持一对一、多对一的关联场景,一对多反向关联(一个Shipment对应多个Timeline)必须使用prefetch_related才能生效。
组装目标结构
拿到queryset后直接遍历组装即可,这一步访问关联的Timeline数据不会触发任何额外的数据库查询:
result = [] for shipment in shipment_queryset: # 关联数据已经预加载到内存,直接取值即可 related_timelines = list(shipment.shipmenttimeline.all()) result.append({shipment: related_timelines})
最终得到的result就是你需要的[{'Shipment 1', ['ShipmentTimeline_x', 'ShipmentTimeline_y']}, ...]结构。
进阶优化(可选)
如果不需要查询ShipmentTimeline的全量字段,可以用Prefetch对象自定义预加载的查询规则,进一步降低数据库IO开销:
from django.db.models import Prefetch # 自定义预加载的Timeline查询规则:只取需要的字段、可追加额外过滤条件 timeline_prefetch_config = Prefetch( lookup="shipmenttimeline", queryset=ShipmentTimeline.objects.only("id", "shipment_id") # 替换为你实际需要的Timeline字段 ) shipment_queryset = Shipment.objects.filter( # 原有过滤条件 ).only("id").prefetch_related(timeline_prefetch_config)
性能对比
- 原有循环写法:总SQL查询次数为
1(查Shipment ID列表) + N(逐ID查Shipment) + N(逐Shipment查关联Timeline),也就是2N+1次查询,数据量越大性能越差。 - 预加载写法:无论匹配到多少条Shipment记录,固定只执行2次SQL查询,性能差距会随数据量增长被显著拉开。
内容的提问来源于stack exchange,提问作者atropa belladona
相关产品推荐
相关产品推荐

