Django REST Serializer 为多个嵌套关系执行 N+1 个数据库调用,3 个级别

标签 django django-models polymorphism django-rest-framework

我的模型有外键关系的情况:

# models.py
class Child(models.Model):
    parent = models.ForeignKey(Parent,)

class Parent(models.Model):
    pass

和我的序列化程序:
class ParentSerializer(serializer.ModelSerializer):
    child = serializers.SerializerMethodField('get_children_ordered')

    def get_children_ordered(self, parent):
        queryset = Child.objects.filter(parent=parent).select_related('parent')
        serialized_data = ChildSerializer(queryset, many=True, read_only=True, context=self.context)
        return serialized_data.data

    class Meta:
        model = Parent

当我在 View 中为 N 个父级调用 Parent 时,Django 在序列化程序中执行 N 个数据库调用,当它抓取子级时。有没有办法让所有 parent 的所有 child 都最小化数据库调用的次数?

我试过这个,但它似乎没有解决我的问题:
class ParentList(generics.ListAPIView):

    def get_queryset(self):
        queryset = Parent.objects.prefetch_related('child')
        return queryset

    serializer_class = ParentSerializer
    permission_classes = (permissions.IsAuthenticated,)

编辑

我已经更新了下面的代码以反射(reflect) Alex 的反馈......它解决了一个嵌套关系的 N+1。
# serializer.py
class ParentSerializer(serializer.ModelSerializer):
    child = serializers.SerializerMethodField('get_children_ordered')

    def get_children_ordered(self, parent):
        # The all() call should hit the cache
        serialized_data = ChildSerializer(parent.child.all(), many=True, read_only=True, context=self.context)
        return serialized_data.data

    class Meta:
            model = Parent

# views.py
class ParentList(generics.ListAPIView):

    def get_queryset(self):
        children = Prefetch('child', queryset=Child.objects.select_related('parent'))
        queryset = Parent.objects.prefetch_related(children)
        return queryset

    serializer_class = ParentSerializer
    permission_classes = (permissions.IsAuthenticated,)

现在假设我还有一个模型,它是一个孙模型:
# models.py
class GrandChild(models.Model):
    parent = models.ForeignKey(Child,)

class Child(models.Model):
    parent = models.ForeignKey(Parent,)

class Parent(models.Model):
    pass

如果我将以下内容放入我的 views.py给家长 queryset :
queryset = Parent.objects.prefetch_related(children, 'children__grandchildren')
看起来这些孙子并没有被带入 ChildSerializer,因此,我再次运行另一个 N+1 问题。对这个有什么想法吗?

编辑 2

也许这会提供清晰度......也许我仍然遇到 N + 1 数据库调用的原因是因为我的 child 和孙子类都是多态的......即
# models.py
class GrandChild(PolymorphicModel):
    child = models.ForeignKey(Child,)

class GrandSon(GrandChild):
    pass

class GrandDaughter(GrandChild):
    pass

class Child(PolymorphicModel):
    parent = models.ForeignKey(Parent,)

class Son(Child):
    pass

class Daughter(Child):
    pass

class Parent(models.Model):
    pass

我的序列化程序看起来更像这样:
# serializer.py
class ChildSerializer(serializer.ModelSerializer):
    grandchild = serializers.SerializerMethodField('get_children_ordered')

    def to_representation(self, value):
        if isinstance(value, Son):
            return SonSerializer(value, context=self.context).to_representation(value)
        if isinstance(value, Daughter):
            return DaughterSerializer(value, context=self.context).to_representation(value)

    class Meta:
        model = Child

class ParentSerializer(serializer.ModelSerializer):
    child = serializers.SerializerMethodField('get_children_ordered')

    def get_children_ordered(self, parent):
        queryset = Child.objects.filter(parent=parent).select_related('parent')
        serialized_data = ChildSerializer(queryset, many=True, read_only=True, context=self.context)
        return serialized_data.data

    class Meta:
        model = Parent

加上孙女,孙子的情况,我会在代码方面为您提供详细信息,但我认为您已经了解了。

当我运行 ParentList 的 View 并监视数据库查询时,我得到了大约 1000 条查询的信息,只有少数 parent 。

如果我在 django shell 中运行相同的代码,我可以在不超过 25 个查询的情况下完成相同的查询。我怀疑这可能与我使用 django-polymorphic 库的事实有关?原因是,除了每个Son/Daughter、Grandson/Granddaughter 表之外,还有一个Child 和GrandChild 数据库表,总共6 个表。穿过那些物体。所以我的直觉告诉我我错过了那些多态表。

或者也许我的数据模型有更优雅的解决方案?

最佳答案

据我所知,嵌套序列化程序可以访问预取关系,只需确保您不修改查询集(即使用 all() ):

class ParentSerializer(serializer.ModelSerializer):
    child = serializers.SerializerMethodField('get_children_ordered')

    def get_children_ordered(self, parent):
        # The all() call should hit the cache
        serialized_data = ChildSerializer(parent.child.all(), many=True, read_only=True, context=self.context)
        return serialized_data.data

    class Meta:
            model = Parent


class ParentList(generics.ListAPIView):

    def get_queryset(self):
        children = Prefetch('child', queryset=Child.objects.select_related('parent'))
        queryset = Parent.objects.prefetch_related(children)
        return queryset

    serializer_class = ParentSerializer
    permission_classes = (permissions.IsAuthenticated,)             

关于Django REST Serializer 为多个嵌套关系执行 N+1 个数据库调用,3 个级别,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35021461/

相关文章:

django - 表上的插入或更新违反了外键约束(错误)

python - 在django模型中获取多对多关联的多对多字段

django - 如何使用 django Rest 框架为不同用户类型创建自定义用户模型

postgresql - 使用 %TYPE 在 PostgreSQL 中声明复合类型的变量

c++ - 通过连续内存实现多态性

django - 日期时间字段中的 unix 时间戳输入

python - 如何通过User在django模板中显示与反向引用的关系?

c++ - C++ 中的多态性和虚函数

python - NoReverseMatch at/Reverse 为 'single_product',未找到任何参数。尝试了 1 个模式 : ['products/(?P<slug>)/$' ]

django - 如何在保存时将整数添加到 Django 模型中的主键字段