Django预取优化查询,但仍然非常缓慢

8

我在一个具有5个m2m字段的模型上使用prefetch_related时遇到了严重的性能问题,并且我还预取了一些嵌套的m2m字段。

class TaskModelManager(models.Manager):
    def get_queryset(self):
        return super(TaskModelManager, self).get_queryset().exclude(internalStatus=2).prefetch_related("parent", "takes", "takes__flags", "assignedUser", "assignedUser__flags", "asset", "asset__flags", "status", "approvalWorkflow", "viewers", "requires", "linkedTasks", "activities")


class Task(models.Model):
    uuid = models.UUIDField(primary_key=True, default=genOptimUUID, editable=False)
    internalStatus = models.IntegerField(default=0)
    parent = models.ForeignKey("self", blank=True, null=True, related_name="childs")
    name = models.CharField(max_length=45)
    taskType = models.ForeignKey("TaskType", null=True)
    priority = models.IntegerField()
    startDate = models.DateTimeField()
    endDate = models.DateTimeField()
    status = models.ForeignKey("ProgressionStatus")
    assignedUser = models.ForeignKey("Asset", related_name="tasksAssigned")
    asset = models.ForeignKey("Asset", related_name="tasksSubject")
    viewers = models.ManyToManyField("Asset", blank=True, related_name="followedTasks")
    step = models.ForeignKey("Step", blank=True, null=True, related_name="tasks")
    approvalWorkflow = models.ForeignKey("ApprovalWorkflow")
    linkedTasks = models.ManyToManyField("self", symmetrical=False, blank=True, related_name="linkedTo")
    requires = models.ManyToManyField("self", symmetrical=False, blank=True, related_name="depends")

    objects = TaskModelManager()

查询数量和数据库查询时间都很好,例如,如果我查询我的模型的 700 个对象,我有 35 个查询,平均查询时间为 100~200ms,但总请求时间约为 8 秒。 丝绸时间 我进行了一些分析,发现超过 80% 的时间花在了 prefetch_related_objects 调用上。 分析 我使用 Django==1.8.5 和 djangorestframework==3.4.6。
我愿意采取任何优化方式。感谢您的帮助。

使用select_related进行编辑:

我尝试了Alasdair提出的改进方法。

class TaskModelManager(models.Manager):
    def get_queryset(self):
        return super(TaskModelManager, self).get_queryset().exclude(internalStatus=2).select_related("parent", "status", "approvalWorkflow", "step").prefetch_related("takes", "takes__flags", "assignedUser", "assignedUser__flags", "asset", "asset__flags", "viewers", "requires", "linkedTasks", "activities")

新的结果对于带有32个查询和150ms查询时间的请求仍然是8秒。

编辑:

看起来4年前在Django问题跟踪器上开了一张票,目前仍然未关闭: https://code.djangoproject.com/ticket/20577

3个回答

13

我遇到了同样的问题。

根据你提供的链接,我发现可以使用Prefetch对象和to_attr参数来提高prefetch_related的性能。

以下是引入Prefetch对象的更改记录:

当Prefetch实例指定一个to_attr参数时,结果将存储在列表而不是QuerySet中。这个有幸的后果是显著地提高了性能。性能的提升归功于我们节省了创建QuerySet实例的代价。

因此,我通过简单调用以下代码显着改进了我的代码(从约7秒减少到0.88秒):

Foo.objects.filter(...).prefetch_related(Prefetch('bars', to_attr='bars_list'))

取代

Foo.objects.filter(...).prefetch_related('bars')

1
尝试使用select_related来处理外键,例如parentApprovalWorkflow,而不是prefetch_related

当您使用select_related时,Django将使用联接检索模型,而prefetch_related则会导致额外的查询。您可能会发现这可以提高性能。


好的,我已经尝试过了,结果并没有真正变得更好。虽然我少了4个查询,但总时间仍然是8秒。我认为问题更多地在于prefetch_related的逻辑而不是SQL查询。 - Lucas Miller

-1

如果数据库只需要150毫秒,但你的请求却需要8秒钟,那么问题不在于你的查询本身(至少不完全是)。可能存在以下几个问题:

1)你的HTML或模板过于复杂,花费了太多时间来生成响应。或者考虑使用模板缓存

2)所有这些对象都很复杂,你加载了太多字段,因此虽然查询很快,但在Python中发送和处理所有这些对象却很慢。尝试使用only()、defer()和values()或value_list()仅加载所需内容。

优化很难,我们需要更多细节才能给出更好的建议。我建议安装Django Debug Toolbar(Django应用程序)或Opbeat(第三方工具),它们可以帮助你检测时间花费在哪里,然后你可以相应地进行优化。


1
可能的主要原因是“python join”,它是由prefetch创建的。 - Mark Mishyn

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