Django使用prefetch_related减少查询

a1o7rhls  于 2023-05-08  发布在  Go
关注(0)|答案(1)|浏览(127)

我正在尝试了解如何改进以下查询:

class PDFUploadRequestViewSet(viewsets.ModelViewSet):

    def get_queryset(self):
        project_id = self.request.META.get('HTTP_PROJECT_ID', None)
        if project_id:
            return PDFUploadRequest.objects.filter(project_id=project_id)
        else:
            return PDFUploadRequest.objects.all()

    def get_serializer_class(self):
        if self.action == 'list':
            return PDFUploadRequestListSerializer
        else:
            return self.serializer_class

问题是,数据库中的PDFPageImage对象越多,它就会为每个对象创建单独的查询,从而减慢请求的速度。如果只有一个值,如果PDFPageImage与给定的PDFUploadRequest相关,那么它的速度相当快,但是对于每个额外的值,它都会产生额外的查询,在做了一些研究之后,我发现prefetch_related可能在某种程度上有助于这一点,但我还没有弄清楚如何在我的模型中使用它。
PDFUploadRequest的模型如下所示:

class PDFUploadRequest(models.Model, BaseStatusClass):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    file = models.FileField(upload_to='uploaded_pdf')
    file_name = models.CharField(max_length=255)
    status = models.CharField(
        max_length=50,
        choices=BaseStatusClass.PDF_STATUS_CHOICES,
        default=BaseStatusClass.UPLOADED,
    )
    completed = models.DateTimeField(null=True)
    processing_started = models.DateTimeField(null=True)
    text = models.TextField(default=None, null=True, blank=True)
    owner = models.ForeignKey(User, related_name='pdf_requests', on_delete=models.PROTECT, null=True, default=None)
    project = models.ForeignKey(Project, related_name='pdf_requests', on_delete=models.PROTECT, null=True, default=None)

    class Meta:
        ordering = ['-created']
    def no_of_pages(self):
        return self.pdf_page_images.count()
    def time_taken(self):
        if self.completed and self.processing_started:
            return self.completed - self.processing_started

这是我认为引起问题的相关模型:

class PDFPageImage(models.Model, BaseStatusClass):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    pdf_request = models.ForeignKey(PDFUploadRequest, related_name="pdf_page_images", on_delete=models.CASCADE)
    image = models.ImageField()
    status = models.CharField(
        max_length=50,
        choices=BaseStatusClass.PDF_STATUS_CHOICES,
        default=BaseStatusClass.UPLOADED,
    )
    page_number = models.IntegerField(null=True, blank=True, default=None)
   
    class Meta:
        ordering = ['page_number']
        constraints = [
            models.UniqueConstraint(fields=['pdf_request', 'page_number'],
                                    condition=models.Q(deleted=False),
                                    name='pdf_request_and_page_number_unique')
        ]

下面是序列化器:

class PDFUploadRequestSerializer(serializers.ModelSerializer):

    pdf_page_images = PDFPageImageSerializer(many=True, read_only=True)

    class Meta:
        model = PDFUploadRequest
        fields = ('id', 'file','file_name', 'status', 'pdf_page_images',
                  , 'owner', 'project')
        read_only_fields = ('file_name', 'pdf_page_images', 'text',
                           'owner', 'project')

我尝试在PDFPageImage模型上使用prefetch_related

PDFUploadRequest.objects.filter(project_id=project_id).prefetch_related("pdf_page_images")

但我不认为它在做任何事情。我能做些什么来减少查询时间呢?

xa9qqrwz

xa9qqrwz1#

当一个查询被发送到数据库时,最好尝试用一个(或几个)查询来获取未来所需的所有数据。当你只是检索对象的数据时,它会试图通过额外的查询来获取额外的数据,这会使服务器变慢。例如,假设我们有一本书,有三个作者(请注意,作者和书籍之间存在多对多的关系,因为一本书可以有多个作者,一个作者可以为多本书做出贡献)。当你从数据库中获取这本书的数据时,它只给你作者的主键。假设您希望在接下来的进程中获得作者的姓名。所以django orm向数据库发送一个查询来获取第一作者的数据,以获取其作者名。然后它需要第二作者的名字。所以它会再一次进入数据库这适用于所有相关对象。
因此,您必须通过使用图书数据预取对图书有贡献的所有作者的数据来优化查询。因此,对于进一步的数据,它不需要任何其他查询。
对于你的代码,我不能在我的系统上运行它,因为我不知道BaseStatusClassPDFPageImageSerializer序列化器的实现是什么。所以我试着读了你的代码。我相信你必须像这样修改你的代码:

PDFUploadRequest.objects.prefetch_related('pdf_page_images').filter(project_id=project_id)

以及:

PDFUploadRequest.objects.prefetch_related('pdf_page_images').all()

另外,请尝试省略get_queryset方法中的if条件。看看django是否会通过只有一个查询来优化查询。我知道该功能需要两个带条件的查询,但请尝试此操作,看看它是否有助于您理解问题。另外,要知道prefetch_related可以与Prefetch类一起使用,以向其发送查询。就像这样:

Book.objects.prefetch_related(Prefetch('comments', Comment.objects.select_related('author').all()))

如果你提供了完整的代码,我会运行它来找到优化的解决方案。

相关问题