如何用Prefetch()结合filter()减少多模型迭代的SELECT查询次数
解决Django链式外键关联查询次数优化问题
问题根源
你遇到的查询次数激增,是因为主QuerySet使用filter()后,若关联对象的预取逻辑未匹配过滤范围,或迭代时额外调用filter(),会触发N+1查询;之前的报错则是因为city_set是State的反向关联属性,并非Country的,不能直接在Country的prefetch_related中指定。
正确实现方案
要保持3次SELECT查询的核心是使用嵌套的Prefetch对象,从最内层关联模型开始定义预取规则,逐层向上嵌套,确保所有关联数据一次性预取完成。
基础版本(无关联对象过滤)
如果仅对Country做过滤,不需要过滤关联的State或City,直接使用链式预取即可:
# 对过滤后的Country,预取关联的所有State及对应的City countries = Country.objects.filter(name__icontains="中").prefetch_related("state_set__city_set") # 迭代时无额外查询 for country in countries: for state in country.state_set.all(): for city in state.city_set.all(): print(f"{country.name} - {state.name} - {city.name}")
这种情况下会执行3次查询:一次过滤Country,一次查询关联的State,一次查询关联的City。
进阶版本(含关联对象过滤)
如果需要对State或City也添加过滤条件,必须将过滤逻辑嵌入Prefetch对象中,避免在迭代时触发额外查询:
from django.db.models import Prefetch # 1. 定义最内层City的预取规则(可添加过滤条件) city_prefetch = Prefetch( "city_set", queryset=City.objects.filter(name__icontains="市") # 仅预取含"市"的城市 ) # 2. 定义State的预取规则,嵌套City的预取(可添加State的过滤条件) state_prefetch = Prefetch( "state_set", queryset=State.objects.filter(name__icontains="省").prefetch_related(city_prefetch) # 仅预取含"省"的省份,并嵌套预取城市 ) # 3. 查询过滤后的Country,使用嵌套的预取规则 countries = Country.objects.filter(name="中国").prefetch_related(state_prefetch) # 迭代时直接使用all(),无需额外filter for country in countries: for state in country.state_set.all(): for city in state.city_set.all(): print(f"{country.name} - {state.name} - {city.name}")
这种方式同样只会执行3次查询:一次过滤Country,一次过滤并查询关联的State,一次过滤并查询关联的City。
关键注意事项
- 禁止在迭代循环中对关联对象(如
state_set、city_set)调用filter(),否则会触发新的查询,导致查询次数激增。 Prefetch对象必须按关联层级嵌套:City属于State,所以要先定义City的预取,再嵌套到State的预取中,最后在Country中使用State的预取。
内容的提问来源于stack exchange,提问作者Super Kai - Kazuya Ito
相关产品推荐
相关产品推荐

