首页 > 解决方案 > Django Serializer 嵌套创建:如何避免对关系的 N+1 查询

问题描述

有几十篇关于 Django 中嵌套关系中的 n+1 查询的帖子,但我似乎找不到我的问题的答案。这是上下文:

模型

class Book(models.Model):
    title = models.CharField(max_length=255)

class Tag(models.Model):
    book = models.ForeignKey('app.Book', on_delete=models.CASCADE, related_name='tags')
    category = models.ForeignKey('app.TagCategory', on_delete=models.PROTECT)
    page = models.PositiveIntegerField()

class TagCategory(models.Model):
    title = models.CharField(max_length=255)
    key = models.CharField(max_length=255)

一本书有很多标签,每个标签都属于一个标签类别。

序列化器

class TagSerializer(serializers.ModelSerializer):
    class Meta:
        model = Tag
        exclude = ['id', 'book']

class BookSerializer(serializers.ModelSerializer):
    tags = TagSerializer(many=True, required=False)

    class Meta:
        model = Book
        fields = ['title', 'tags']

    def create(self, validated_data):
        with transaction.atomic():
            tags = validated_data.pop('tags')
            book = Book.objects.create(**validated_data)
            Tag.objects.bulk_create([Tag(book=book, **tag) for tag in tags])
        return book

问题

我正在尝试BookViewSet使用以下示例数据发布到:

{ 
  "title": "The Jungle Book"
  "tags": [
    { "page": 1, "category": 36 }, // plot intro
    { "page": 2, "category": 37 }, // character intro
    { "page": 4, "category": 37 }, // character intro
    // ... up to 1000 tags
  ]
}

这一切都有效,但是,在发布期间,序列化程序继续调用每个标签以检查它是否category_id是有效的:

在此处输入图像描述

一次调用中最多有 1000 个嵌套标签,我负担不起。
我如何“预取”验证?
如果这是不可能的,我该如何关闭检查 foreign_key id 是否在数据库中的验证?

编辑:附加信息

这是视图:

class BookViewSet(views.APIView):

    queryset = Book.objects.all().select_related('tags', 'tags__category')
    permission_classes = [IsAdminUser]

    def post(self, request, format=None):
        serializer = BookSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

标签: pythondjangodjango-rest-frameworkdjango-serializer

解决方案


DRF 序列化程序不是优化数据库查询的地方(在我自己看来)。序列化器有 2 个工作:

  1. 序列化并检查输入数据的有效性。
  2. 序列化输出数据。

因此,优化查询的正确位置是相应的视图。
我们将使用以下select_related方法:

返回一个 QuerySet,它将“遵循”外键关系,在执行查询时选择其他相关对象数据。这是一个性能提升器,它会导致单个更复杂的查询,但意味着以后使用外键关系将不需要数据库查询。避免 N+1 数据库查询。

您需要修改创建相应查询集的视图代码部分,以便包含select_related调用。
您还需要related_nameTag.category字段定义中添加一个。

示例

# In your Tag model:
category = models.ForeignKey(
    'app.TagCategory', on_delete=models.PROTECT, related_name='categories'
)

# In your queryset defining part of your View:
class BookViewSet(views.APIView):

    queryset = Book.objects.all().select_related(
        'tags', 'tags__categories'
    )  # We are using the related_name of the ForeignKey relationships.

如果你想测试一些不同的东西,也使用序列化器来减少查询的数量,你可以查看这篇文章


推荐阅读