Django prefetch_related 优化查询但仍然很慢

Django prefetch_related optimize query but still very slow

我在具有 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~200 毫秒,但总请求时间约为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次,查询时间为150ms的请求,新结果仍然是8秒。


编辑:

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

尝试使用 select_related 作为外键,例如 parentApprovalWorkflow 而不是 prefetch_related

当您使用 select_related 时,Django 将使用连接获取模型,这与 prefetch_related 不同,后者会导致额外的查询。您可能会发现这会提高性能。

如果数据库是 150 毫秒,但您的请求是 8 秒,则这不是您的查询(至少就其本身而言)。一些可能的问题:

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

2) 所有这些对象都很复杂并且您加载了太多字段,因此虽然查询速度很快,但发送和处理 Python 中的所有这些对象却很慢。探索使用 only()、defer() 和 values() 或 value_list() 只加载您需要的内容。

优化很难,我们需要更多的细节来给你一个更好的主意。我建议安装 Django 调试工具栏(Django 应用程序)或 Opbeat(第 3 方实用程序),它们可以帮助您检测您的时间花在了哪里,然后您可以相应地进行优化。

我运行遇到了同样的问题。

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

来自引入Prefetch对象的commit

When a Prefetch instance specifies a to_attr argument, the result is stored in a list rather than a QuerySet. This has the fortunate consequence of being significantly faster. The preformance improvement is due to the fact that we save the costly creation of a QuerySet instance.

所以我显着改进了我的代码(从大约 7 秒到 0.88 秒),只需调用:

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

而不是

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