Django 避免N+;1使用引用反向外键关系的属性序列化模型时的查询
我目前遇到一个问题,Django的Django 避免N+;1使用引用反向外键关系的属性序列化模型时的查询,django,django-rest-framework,Django,Django Rest Framework,我目前遇到一个问题,Django的prefetch_related()和prefetch_related_objects()都不足以优化Django Rest框架序列化,我很好奇这里是否有人有一些想法 为了形象化我所处的情况,假设我有三个模型,它们相互关联: class VotableObjectExampleParent(models.Model): #several other fields redacted here# class VotableObjectExample(mod
prefetch_related()
和prefetch_related_objects()
都不足以优化Django Rest框架序列化,我很好奇这里是否有人有一些想法
为了形象化我所处的情况,假设我有三个模型,它们相互关联:
class VotableObjectExampleParent(models.Model):
#several other fields redacted here#
class VotableObjectExample(models.Model):
#several other fields redacted here#
votableobjectexampleparent = models.ForeignKey('VotableObjectExampleParent', on_delete=models.CASCADE, related_name = 'votableobjectexamples')
@property
def total_votes(self):
return self.votableobjectexample_votes.aggregate(sum = Sum("vote"))["sum"] or 0
class VotableObjectExample_Vote(models.Model):
#several other fields redacted here#
votablebjectexample = models.ForeignKey('VotableObjectExample', on_delete=models.CASCADE, related_name = 'votablebjectexample_votes')
vote = models.IntegerField(choices = [(-1, "down"), (1, "up")])
然后,我有了适当的(功能性的)DRF序列化程序,如图所示:
class VotableObjectExampleParent_Serializer(serializers.ModelSerializer):
related_votableobjectexamples = Idea_Proposal_Serializer(source = "votableobjectexamples", many=True, read_only=True)
class Meta:
model = VotableObjectExampleParent
fields = ['related_votableobjectexamples'] #other fields would normally be in this array
read_only_fields = fields
class VotableObjectExample_Serializer(serializers.ModelSerializer):
class Meta:
model = VotableObjectExample
fields = ['total_votes'] #other fields would normally be in this array
read_only_fields = fields
首先,我只需在视图中调用父序列化程序(VotableObjectExampleParent\u序列化程序
):
#assume a variable I already have called this_pk is correct and available for use
serializer = VotableObjectExampleParent_Serializer(VotableObjectExampleParent.objects.get(pk = this_pk), many = True)
JSONRenderer().render(serializer.data)
这产生了100%符合我需要的正确JSON,但我很快就遇到了N+1查询的问题,因为有大量的VotableObjectExamples
和VotableObjectExample_投票
,在现实生活中,还有大量其他嵌套的反向外键关系需要遍历。通过使用prefetch\u related\u objects()
替换上面的代码块,我能够将这需要的30个查询(很容易扩展到100个)减少到20个:
this_votableobjectparent = [VotableObjectExampleParent.objects.get(pk = this_pk)]
prefetch_related_objects(this_votableobjectparent, 'votableobjectexamples', 'votableobjectexamples__votablebjectexample_votes')
JSONRenderer().render(VotableObjectExampleParent_Serializer(this_votableobjectparent[0]).data)
使用fantasticDjango调试工具栏
我可以验证是否正确地进行了查询,以便在上面所有指定的模型中预取正确的ID。尽管如此,仍有N+1个查询仅用于计算总投票数
字段
我从不同的StackOverflow线程中尝试了一百万种不同的建议,比如从使用aggregate
切换到annotate
:
@property
def total_votes(self):
query = self.votablebjectexample_votes.annotate(sum = Sum("vote")).values_list('sum',flat=True)
if query:
return query[0]
else:
return 0
我还尝试为每个模型添加一个自定义的Manager
,该模型带有total\u voces
字段,将total\u voces计算移动到get\u queryset方法。不幸的是,在这种情况下,DRF抱怨它根本找不到total_voces
字段,我怀疑这是因为该字段不再在ModelSerializer
指向的模型中显式声明
有人知道从这里到哪里去吗?也许有一个简单的解决方案,但对于某些人来说,这似乎不是一个特别不寻常的问题,所以我有点惊讶,需要这么多的修补才能产生高效和可扩展的东西
提前感谢您的帮助 可能是
N+1
的根本原因是VotableObjectExample.total\u Voces
属性,因为self.VotableObjectExample\u Voces
将创建另一个查询
- 使用
时,它会存储在预回迁\u related
(请注意,如果未执行预回迁,它将导致实例中。_prefetched\u objects\u cache
)。如果AttributeError
存在,则它是instance.\u prefetched\u objects\u cache
的{”“:,…}
dict
- 另一点是,即使使用了
,在缓存的queryset中添加更多查询也会创建另一个查询。在这种情况下,运行以下行将导致另一个查询,因为聚合是另一个查询:实例。_prefetched_objects_cache
self._prefetched_objects_cache["votableobjectexample_votes"].aggregate(sum = Sum("vote"))["sum"]
总之,你可以试试这个:
class VotableObjectExample(models.Model):
#several other fields redacted here#
@property
def total_votes(self):
# Check if prefetch cache exists
if hasattr(self, "_prefetched_objects_cache") and self._prefetched_objects_cache.get("votableobjectexample_votes"):
total_votes = 0
for votableobjectexample_vote in votableobjectexample_votes:
total_votes += votableobjectexample_vote.vote
return total_votes
# Still need this if `prefetch_related` wasn't called
return self.votableobjectexample_votes.aggregate(sum = Sum("vote"))["sum"] or 0
可伸缩:将作业放入队列中,以计算和存储每个投票事件的总投票数,而不是在查询时计算。或者,实现一个查询缓存,并在每次投票事件中插入一个作业来更新它。@bryan60,这很有意义。我仍然很好奇如何通过查找来优化属性(谁知道什么时候该问题会合法出现!),但我必须承认,在解决我的特定问题时,您是正确的。我需要考虑如何在这里编写SQL。感觉可以对此进行优化,但可能是使用了
@property
装饰器弄乱了ORM。我会先设法解决这个问题,不让事情复杂化,然后看看你能不能在事后再把那个部分放进去。DRF很棒,但序列化程序会将一些愚蠢的查询留给自己的设备。应该有一种方法让它在一个查询中计算并注释所有投票总数,而不是在每个votable对象的查询中。