Django:prefetch_lated 没有效果

问题描述 投票:0回答:3

我正在尝试使用

prefetch_related
优化数据库查询,但没有成功。

模型.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)

运行 shell,我可以看到问题:

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_lated Django 文档中发现了这一点

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

因此,每次运行新查询时调用

latest()
似乎很正常。

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

python django django-models query-performance
3个回答
1
投票

由于 OrderOperation 仅包含一个相关字段,

total
,更好的方法是使用子查询来注释原始查询中最新操作的总数:

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]))

0
投票

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

而不是调用

latest()
,如果我只是使用
[0]
获取查询集中的第一项(或者使用
len(qs)-1
获取最后一项,假设 order_operations 已经订购了,会怎样?

@property
def last_operation(self) -> Optional['OrderOperation']:
    try:
        qs = self.orderoperation_set.all()
        return qs[len(qs) - 1]
    except IndexError:
        return None

0
投票

为了避免从数据库中检索所有

OrderOperation
(正如 @Daniel Roseman 在您的答案中评论的那样),您可以限制预取操作将使用的查询集:

from django.db.models import Prefetch

Order.objects.prefetch_related(
    Prefetch(
        "orderoperation_set",
        OrderOperation.objects.order_by("-created_at")[:1]
    )
)
@property
def last_operation(self):    
    if orderoperation_set := self.orderoperation_set.all()
        return orderoperation_set[0]
    return None

      

请注意使用

.all()[0]
而不是
.first()
,因为这会触发新的数据库查询,就像
.latest()
所做的那样。

注释是最好的方法,因为它给你一个固定的值,所以你在使用它时不必那么谨慎。

© www.soinside.com 2019 - 2024. All rights reserved.