是否应依赖Django _prefetched_objects_cache解决N+1查询问题?
_prefetched_objects_cache及更优方案 一、是否应该依赖_prefetched_objects_cache?
不建议依赖。_prefetched_objects_cache是Django内部用于存储预取数据的属性,并未在官方文档中公开其结构与兼容性承诺。Django版本迭代可能修改该内部实现,直接依赖会导致代码在未来版本出现兼容性问题,属于非官方的hack手段,维护风险较高。
二、更优的解决方案
针对你的场景,有几种官方稳定的方案可以消除N+1查询,同时满足排序需求:
方案1:预取时通过Prefetch对象指定排序
在查询TheObject时,使用Prefetch类明确指定关联parts的排序规则,让预取的数据直接是排好序的,无需在模型方法中额外触发查询。
修改查询代码:
from django.db.models import Prefetch ofs = ObjectFormSet( queryset=TheObject.objects .filter(objectset=os) .prefetch_related( Prefetch( "parts", queryset=Part.objects.order_by("pk") ) ) )
修改TheObject的parts_ordered方法:
class TheObject(models.Model): # ... def parts_ordered(self) -> list["Part"]: # 直接返回预取好的排序后的数据,不会触发新查询 return list(self.parts.all())
优点:灵活性高,仅在当前查询场景生效,不会影响其他地方对Part模型的查询逻辑。
方案2:给Part模型设置默认排序
如果按pk排序是Part模型的全局业务默认规则,可以在Part的Meta类中指定默认排序,所有关联查询都会自动应用该规则,预取数据也会保持有序。
修改Part模型:
class Part(models.Model): theobject = models.ForeignKey( TheObject, on_delete=models.CASCADE, related_name="parts" ) # ... 其他字段 class Meta: ordering = ["pk"]
修改parts_ordered方法:
class TheObject(models.Model): # ... def parts_ordered(self) -> list["Part"]: # 此时self.parts.all()默认按pk排序,预取数据也会遵循该规则 return list(self.parts.all())
优点:一劳永逸,所有涉及Part的查询都会自动按pk排序,无需重复编写排序逻辑。
方案3:使用自定义管理器实现关联排序
如果不想给Part全局设置排序,但希望特定关联关系(如TheObject.parts)默认排序,可以自定义模型管理器。
首先定义自定义管理器:
class OrderedPartManager(models.Manager): def get_queryset(self): return super().get_queryset().order_by("pk")
将其应用到Part模型:
class Part(models.Model): theobject = models.ForeignKey( TheObject, on_delete=models.CASCADE, related_name="parts" ) # ... 其他字段 # 全局使用排序管理器 objects = OrderedPartManager()
之后parts_ordered方法同样可以直接返回list(self.parts.all()),无需额外排序。
优点:可以局部控制排序规则,既不影响全局Part查询,也无需在每次预取时重复配置。
三、总结
优先选择上述官方方案替代依赖_prefetched_objects_cache的hack手段,其中**方案1(Prefetch指定排序)**是最通用、风险最低的选择,适合大多数场景;如果排序是全局默认规则,则方案2更简洁。
内容的提问来源于stack exchange,提问作者dfrankow

