Django:prefetch_related无效

时间:2018-07-26 09:52:25

标签: python django django-models query-performance

我正在尝试使用prefetch_related优化数据库查询,但没有成功。

models.py

class Order(models.Model):
    # some fields ...

    @property
    def last_operation(self) -> Optional['OrderOperation']:
        try:
            return self.orderoperation_set.latest()
        except OrderOperation.DoesNotExist:
            return None

    @property
    def total(self) -> Optional[Decimal]:
        last_operation = self.last_operation
        return last_operation.total if last_operation else None

class OrderOperation(TimeStampable, models.Model):
    order = models.ForeignKey(Order)
    total = DecimalField(max_digits=9, decimal_places=2)

运行外壳,我可以看到问题:

orders = Order.objects.prefetch_related('orderoperation_set')  # There are 1000 orders
result = sum([order.total for order in orders])
len(connection.queries)
>>> 1003

我们可以看到,每个order.total有一个查询,因此有1000个查询,这使整个请求非常糟糕,性能与订单数成线性关系。

试图了解为什么会这样,我在prefetch_related Django doc中发现了这一点:

  

请记住,与QuerySet一样,任何暗示不同数据库查询的后续链接方法都将忽略先前缓存的结果,并使用新的数据库查询来检索数据。

因此,每次调用latest()都会运行一个新查询似乎很正常。

在这种情况下,您将如何提高性能?(执行一些查询而不是N,其中N是订单数)。

2 个答案:

答案 0 :(得分:1)

由于OrderOperation仅包含单个相关字段total,因此更好的方法是使用subquery注释原始查询中最新操作的总数:

from django.db.models import OuterRef, Subquery
newest = OrderOperation.objects.filter(post=OuterRef('pk')).order_by('-created_at')  # or whatever the timestamp field is
orders = Order.objects.annotate(newest_operation_total=Subquery(newest.values('total')[:1]))

答案 1 :(得分:0)

我在这里发布答案,你能告诉我这是否有意义吗?

如果我只用latest()来获取查询集中的第一项(或者用[0]来获取我的查询集中的第一项,而又假设order_operation已被排序,该怎么办呢?

len(qs)-1