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

Django ORM查询优化咨询:带软删除过滤的预取查询是否无N+1问题

Django ORM查询优化确认与改进建议

问题背景

  • 已在MemosInProduct模型中添加软删除字段_is_deleted,模型定义:
class MemosInProduct(models.Model):
    product_key=models.ForeignKey(ProductInOrder, on_delete=models.CASCADE, related_name="product_key")
    memo=models.CharField(max_length=100)
    _is_deleted=models.BooleanField(default=False)
    # 其他代码...
  • 查询目标:获取所有OrderList数据及其关联的ProductInOrder、未被软删除的MemosInProduct数据,需支持order_list[0].order_key[0].product_key[0].memo这类链式访问。

当前使用的ORM查询

OrderList.objects.prefetch_related(
    Prefetch('order_key', queryset=ProductInOrder.objects.prefetch_related(
        Prefetch('product_key', queryset=MemosInProduct.objects.all().filter(_is_deleted=False))).all()
    )
).all()

疑问解答

1. 是否存在N+1查询问题?

该查询不存在N+1问题。
你使用了嵌套的Prefetch配合prefetch_related,Django会执行以下3次批量查询:

  • 一次查询所有OrderList数据
  • 一次批量拉取所有关联的ProductInOrder数据
  • 一次批量拉取所有_is_deleted=False的MemosInProduct数据
    后续访问关联对象时不会触发额外查询,完全避免了N+1问题。

2. 查询的改进空间

  • 移除多余的.all()调用:
    ProductInOrder.objects.prefetch_related(...).all()和MemosInProduct.objects.all().filter(...)中的.all()是冗余的,查询集在调用filter或传递给Prefetch时无需额外调用.all(),简化后代码:
    OrderList.objects.prefetch_related(
        Prefetch('order_key', queryset=ProductInOrder.objects.prefetch_related(
            Prefetch('product_key', queryset=MemosInProduct.objects.filter(_is_deleted=False))
        ))
    )
    
  • 优化related_name命名:
    当前外键的related_name="product_key"与字段名重复,可读性差,建议改为更语义化的名称,比如related_name="memos",这样访问关联数据时可以用product_in_order.memos替代product_in_order.product_key,逻辑更清晰。
  • 封装软删除逻辑到Manager:
    给MemosInProduct添加自定义管理器,默认过滤已删除数据,避免每次查询重复写filter(_is_deleted=False):
    class MemosInProductManager(models.Manager):
        def get_queryset(self):
            return super().get_queryset().filter(_is_deleted=False)
    
    class MemosInProduct(models.Model):
        product_key=models.ForeignKey(ProductInOrder, on_delete=models.CASCADE, related_name="memos")
        memo=models.CharField(max_length=100)
        _is_deleted=models.BooleanField(default=False)
        # 默认管理器返回未删除数据
        objects = MemosInProductManager()
        # 保留原始管理器用于查询所有数据(包括已删除)
        all_objects = models.Manager()
    
    之后查询未删除数据只需写MemosInProduct.objects.all(),代码更简洁。
  • 规范字段命名:
    模型字段_is_deleted的下划线通常用于标识私有属性,但Django模型字段本质是公开的,建议改为is_deleted,符合Python和Django的常规命名规范。

内容的提问来源于stack exchange,提问作者Loire

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 10:30:38