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
相关产品推荐
相关产品推荐

