Django REST框架序列化POST请求速度慢

4
我是一个有用的助手,可以为您翻译文本。
我正在使用Django 2.1.1和Python 3.6.5,并执行一个相当大的POST操作(32,000个JSON对象)。我有以下内容:
模型:
class Data(models.Model):
    investigation = models.ForeignKey(Investigation)
    usage = models.FloatField()
    sector = models.CharField(max_length=100, blank=False, default='')
    cost = models.FloatField()
    demand = models.FloatField()

序列化器:

class DataSerializer(serializers.ModelSerializer):
    class Meta:
        model = Data
        fields = ('investigation', 'usage', 'sector', 'cost', 'demand')

查看:

class DataView(generics.CreateAPIView):
    def create(self, request, pk, format=None):
        data_serializer = DataSerializer(data=request.data, many=True)
        if data_serializer.is_valid():
            data_serializer.save()

问题出现在is_valid()和save()步骤中,每个步骤都会为32,000个对象触发单独的查询。
我花了很长时间来研究这个问题,猜测is_valid()步骤因为每次查找外键(虽然我可能错了!)而变慢,这是由于N+1查询问题导致的。但我不知道如何在这个框架中实现prefetch_related方法。
显然,最慢的部分——save()步骤需要通过一个查询完成(可能是bulk_create),但我找不到添加bulk_create步骤的位置。我阅读了this question,但从答案中仍然没有得到答案。我尝试创建一个ListSerializer,如问题所建议的那样,但似乎仍然是逐个序列化对象。任何指针将不胜感激。

1
你可以查看这篇帖子,或许会对你有所帮助。 - Aurora Wang
@AuroraWang 我认为这并没有什么帮助。每个验证步骤和每个创建都仍然需要查询。我认为这只是在视图中使用 many = True 的另一种方式。 - Tom
@Tom 你最终找到解决方案了吗? - darcyy
@darcyy,请遵循pradeepcep的解决方案。 - Kishore Sampath
1
比不做好,晚点还是可以的@Tom,请看一下我的SO帖子,在那里我解决了is_valid()中N+1验证问题。我演示了如何在使用外键名称而不是ID-pk发布项目时进行操作,然而我也让它能够仅使用主键的ID运行。https://stackoverflow.com/questions/67111304/drf-selected-related-on-is-valid - Thorvald
2个回答

2

一种可能的解决方案是在使用序列化器验证数据后,执行Django ORM bulk_create()。你的视图将如下所示:

class DataView(generics.CreateAPIView):
    def create(self, request, pk, format=None):
        data_serializer = DataSerializer(data=request.data, many=True)
        if data_serializer.is_valid():
            data_objects = []
            for data_object_info in data_serializer.validated_data:
                data_objects.append(Data(**data_object_info))
            Data.objects.bulk_create(data_objects)

如果你只想要一行代码,那么可以使用以下内容:

最初的回答

Data.objects.bulk_create([Data(**params) for params in data_serializer.validated_data])

如果您不想使视图混乱,那么您可以编写一个类或方法来执行验证(使用序列化器)和创建。然后在视图中使用此内容。原始答案应翻译为“最初的回答”。

1
感谢@pradeepcep。这显著减少了将数据插入数据库所需的时间,非常适合我的用例。 - Kishore Sampath

-1
您可以尝试通过覆盖序列化器的create方法来实现,具体如下:
def create(self, request):
    is_many = True if isinstance(request.data, list) else False

    serializer = self.get_serializer(data=request.data, many=is_many)
    serializer.is_valid(raise_exception=True)
    self.perform_create(serializer)
    headers = self.get_success_headers(serializer.data)
    return Response(serializer.data, status=status.HTTP_201_CREATED,headers=headers)

我觉得你刚刚只是复制了上面的注释中的代码,这并没有什么帮助。请看看我的评论,了解为什么这样做不起作用。 - Tom

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接