【问题标题】:Django prefetch_related optimize query but still very slowDjango prefetch_related 优化查询但仍然很慢
【发布时间】:2017-03-16 15:13:48
【问题描述】:

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


编辑:

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

【问题讨论】:

    标签: python django postgresql prefetch


    【解决方案1】:

    我遇到了同样的问题。

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

    来自引入Prefetch 对象的commit

    当 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')
    

    【讨论】:

      【解决方案2】:

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

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

      【讨论】:

      • 好的,我试过了,结果并没有好到哪里去。我少了 4 个查询,但总时间仍然是 8 秒。我认为问题更多在于 prefetch_related 的逻辑而不是 sql 查询。
      【解决方案3】:

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

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

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

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

      【讨论】:

      • 可能主要原因是prefetch制作的“python join”。
      猜你喜欢
      • 1970-01-01
      • 2022-01-01
      • 1970-01-01
      • 2022-11-18
      • 2021-12-13
      • 1970-01-01
      • 2014-02-05
      • 1970-01-01
      • 2021-08-19
      相关资源
      最近更新 更多