Django prefetch_related优化查询,但仍然非常慢

时间:2017-03-16 15:13:49

标签: python django postgresql prefetch

我在具有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秒

silk times

我进行了一些分析,并指出超过80%的时间花在了prefetch_related_objects电话上。

profiling

我正在使用Django==1.8.5djangorestframework==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个查询和150毫秒查询时间的请求,新结果仍为8秒。

修改:

似乎4年前在Django问题跟踪器上打开了一张票,并且仍在打开:https://code.djangoproject.com/ticket/20577

3 个答案:

答案 0 :(得分:1)

尝试将select_related用于parentApprovalWorkflow等外键,而不是prefetch_related

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

答案 1 :(得分:0)

如果数据库是150毫秒,但您的请求是8秒,那么它不是您的查询(至少本身)。一些可能的问题:

1)您的HTML或模板过于复杂,在生成响应时花费了太多时间。或者考虑template caching.

2)所有这些对象都很复杂,你加载的字段太多,所以当查询速度很快时,发送它并在Python中处理所有这些对象的速度很慢。探索仅使用(),defer()和values()或value_list()来加载你需要的东西。

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

答案 2 :(得分:0)

我遇到了同样的问题。

issue you linked之后,我发现可以使用prefetch_related对象和Prefetch参数来提高to_attr的性能。

从引入了Prefetch对象的commit

  

当Prefetch实例指定to_attr参数时,结果为   存储在列表中而不是QuerySet中。这很幸运   明显更快的结果。性能改进   是由于我们节省了创建查询集的昂贵开销   实例。

因此,只需调用以下命令,我就可以将我的代码从7秒显着提高到0.88秒。

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

代替

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