python - 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)
解决方案
DRF 序列化程序不是优化数据库查询的地方(在我自己看来)。序列化器有 2 个工作:
- 序列化并检查输入数据的有效性。
- 序列化输出数据。
因此,优化查询的正确位置是相应的视图。
我们将使用以下select_related
方法:
返回一个 QuerySet,它将“遵循”外键关系,在执行查询时选择其他相关对象数据。这是一个性能提升器,它会导致单个更复杂的查询,但意味着以后使用外键关系将不需要数据库查询。避免 N+1 数据库查询。
您需要修改创建相应查询集的视图代码部分,以便包含select_related
调用。
您还需要related_name
在Tag.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.
如果你想测试一些不同的东西,也使用序列化器来减少查询的数量,你可以查看这篇文章。
推荐阅读
- cors - Azure 广告身份验证 Asp.net Core 2.2 和 Angular 8.2 客户端的 CORS 问题
- node.js - JOI不能按要求设置确认密码
- python - id 该字段是必需的 - Django formset 中的错误
- mysql - 从同一个表mysql存储过程中选择具有不同条件的多个计数
- javascript - BABYLON.AnimationEvent 的行为不符合预期
- google-cloud-firestore - Firestore 限制数组字段条目
- excel - 如何使用 VBA 将宏粘贴到其他工作表
- google-api - 使用 admin sdk API 无法使用“OAuth 2.0 for Server to Server Applications”协议读取用户
- bash - 循环中内置的bash调用变量
- sql - SQL - LAG 函数 - 运行总计