Django prefetch_related是否支持三层关联查询?如何优化多表查询速度?
prefetch_related完全支持三层甚至更多层的链式关联查找,你原有视图里的example_three__example_two__example_one写法本身是合法的,查询耗时过长的核心原因是代码中多处操作跳过了预取缓存,触发了大量N+1的重复数据库请求,以下是具体优化方案:
现有代码的核心问题
- 序列化器存在低级笔误:所有序列化器的Meta属性中
model字段全部错误指向了ExampleOne,实际应该对应自身的模型类(ExampleTwoSerializer对应ExampleTwo,以此类推),先修正这个问题再做后续优化。 - 预取缓存未被正确使用:
- 模型属性中调用的
exists()方法默认不会复用预取的缓存,会直接发起新的数据库查询 - 序列化器中调用的
latest()方法也不会复用预取的关联数据集,会触发新查询
- 模型属性中调用的
- 序列化器禁止做ORM查询:prefetch操作只有在QuerySet访问数据库之前调用才会生效,序列化器运行时QuerySet已经完成数据库查询,此时在序列化器中调用任何ORM查询方法都会产生冗余请求,完全不会复用视图层的预取结果。
具体优化步骤
1. 改写视图层预取逻辑,提前按业务需求预排序关联数据
针对你需要取ExampleOne最新that_date记录的需求,预取阶段就完成排序,后续直接取内存中的数据即可:
from django.db.models import Prefetch # 预取ExampleOne,按that_date倒序,存入自定义属性避免覆盖默认关联 prefetch_example_one = Prefetch( 'example_one', queryset=ExampleOne.objects.order_by('-that_date'), to_attr='prefetched_example_one_sorted' ) # 预取ExampleTwo,关联上已经排序好的ExampleOne prefetch_example_two = Prefetch( 'example_two', queryset=ExampleTwo.objects.prefetch_related(prefetch_example_one) ) # 预取ExampleThree,关联上已经处理好的ExampleTwo prefetch_example_three = Prefetch( 'example_three', queryset=ExampleThree.objects.prefetch_related(prefetch_example_two) ) # 最终查询ExampleFour时使用预取链 qs = ExampleFour.objects.prefetch_related(prefetch_example_three).filter(...)
2. 改写模型属性,全部使用预取缓存避免新查询
修正ExampleThree的get_calculation_one属性:
@property def get_calculation_one(self): # 直接读取预取到内存的数据集,不会触发新查询 example_one_list = self.example_two.prefetched_example_one_sorted if not example_one_list: return 0 today = datetime.today() for example1 in example_one_list: if example1.option is not None: if self.this_date >= example1.that_date: return self.number * example1.money.amount else: if self.this_date >= today: return self.number * example1.money.amount return 0
修正ExampleFour的get_calculation_two属性:
@property def get_calculation_two(self): cost = 0 # 直接遍历预取好的example_three,无需调用exists判断 for example3 in self.example_three.all(): cost += example3.get_calculation_one return cost
3. 改写序列化器方法,仅读取内存中已有的预取数据
修正ExampleTwoSerializer的get_example_1_value方法:
def get_example_1_value(self, obj): # 预取时已经按that_date倒序,直接取第一个元素即为最新记录 if obj.prefetched_example_one_sorted: return ExampleOneSerializer(obj.prefetched_example_one_sorted[0]).data return None
额外性能提升建议
- 关联字段、排序字段、过滤字段全部加数据库索引,可大幅降低数据库查询耗时
- 如果不需要返回嵌套的关联详情,仅需要计算结果的话,可以把计算逻辑迁移到数据库层,用Django的
annotate配合Case When、Sum等数据库函数直接计算出结果,避免Python层循环 - 数据量极大的场景可以考虑分页返回,避免一次性加载和序列化过多数据
内容的提问来源于stack exchange,提问作者Mapelo Msiwa
相关产品推荐
相关产品推荐

