You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Django prefetch_related是否支持三层关联查询?如何优化多表查询速度?

Django prefetch_related三层关联查询优化方案

prefetch_related完全支持三层甚至更多层的链式关联查找,你原有视图里的example_three__example_two__example_one写法本身是合法的,查询耗时过长的核心原因是代码中多处操作跳过了预取缓存,触发了大量N+1的重复数据库请求,以下是具体优化方案:


现有代码的核心问题

  1. 序列化器存在低级笔误:所有序列化器的Meta属性中model字段全部错误指向了ExampleOne,实际应该对应自身的模型类(ExampleTwoSerializer对应ExampleTwo,以此类推),先修正这个问题再做后续优化。
  2. 预取缓存未被正确使用:
    • 模型属性中调用的exists()方法默认不会复用预取的缓存,会直接发起新的数据库查询
    • 序列化器中调用的latest()方法也不会复用预取的关联数据集,会触发新查询
  3. 序列化器禁止做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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.29 04:45:01