关于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
- 问题 *
我尝试使用以下示例数据POST到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
]
}
这一切都可以工作,但是,在post期间,序列化器继续为每个标记进行调用,以检查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)
6条答案
按热度按时间fsi0uk1n1#
DRF序列化器不是优化DB查询的地方(在我自己看来)。序列化器有2个工作:
1.序列化并检查输入数据的有效性。
1.序列化输出数据。
因此,优化查询的正确位置是相应的视图。
我们将使用
select_related
方法:返回一个QuerySet,它将“跟随”外键关系,在执行查询时选择其他相关对象数据。这是一个性能提升器,它会导致一个更复杂的查询,但意味着以后使用外键关系将不需要数据库查询。以避免N+1个数据库查询。
您需要修改视图代码中创建相应查询集的部分,以便包含
select_related
调用。您还需要将
related_name
添加到Tag.category
字段定义中。如果你想测试一些不同的东西,也使用序列化器来减少查询的数量,你可以检查this article。
vc9ivgsu2#
我认为这里的问题是
Tag
构造函数会通过从数据库中查找来自动将传入的类别id转换为category
示例。避免这种情况的方法是,如果您知道所有类别id都是有效的,请执行以下操作:hlswsv353#
我想出了一个让事情运转起来的答案(但我并不感到兴奋):像这样修改标记序列化器:
这允许我读/写category_id而不需要验证的开销。添加
category
到exclude确实意味着如果在示例上设置了category
,序列化器将忽略它。wsxa1bj14#
问题是,您没有将创建的标记设置到book示例,因此序列化程序在返回时会尝试获取此标记。
您需要将其设置为图书列表:
为TagSerializer提供 meta.fields tuple(奇怪的是,这个序列化器没有提示需要fieldstuple)
在这种情况下,预取tag.category应该是不必要的,因为它只是id。
需要为GET方法预取Book.tags,最简单的方法是为序列化器创建静态方法,并在viewset get_queryset方法中使用,如下所示:
p8ekf7hl5#
select_related
函数会在第一次检查ForeignKey。实际上,这是关系数据库中的ForeignKey检查,您可以使用数据库中的SET FOREIGN_KEY_CHECKS=0;
关闭检查。mzsu5hc06#
我知道这个问题已经存在了很长一段时间,但我有同样的问题,我找了几天的解决方案,最后我找到了另一个解决方案,为我工作。
我把它留在这里,以防它对某些人有帮助,这样它就不再对每个关系进行查询,现在它只是对所有关系的查询,在
to_internal_value
中它验证外键。