首页 > 解决方案 > 通过预取提高性能并选择相关

问题描述

我正在尝试使用模型订单中的信息导出 csv,该订单与交付渠道和餐厅具有 1 对 1 的关系,与订单线具有 1 对多的关系。下载它需要太多时间(10k 行大约需要 20 秒)。

这是我的代码:

orderlines = OrderLine.objects.select_related("product").only(
                "product__display_name", "quantity", "paid_amount", "discount_amount"
            )
return (
    Order.objects.prefetch_related(Prefetch("orderlines", queryset=orderlines, to_attr="orderlines_list"))
    .select_related("delivery_channel")
    .select_related("restaurant")
)

这些是我的模型:

class Order(TimeStampedModel, TenantModel):

    id = models.AutoField
    objects = OrderManager()

    restaurant = models.ForeignKey(
        Restaurant,
        blank=False,
        null=False,
        on_delete=models.PROTECT,
        help_text=_("References the restaurant the order is for"),
    )

    delivery_channel = models.ForeignKey(
        DeliveryChannel,
        blank=False,
        null=False,
        on_delete=models.PROTECT,
    )

class Restaurant(TenantModel):
    id = models.AutoField
    name = models.CharField(max_length=255, blank=False, null=False, default="", unique=False)

class DeliveryChannel(models.Model):
    id = models.AutoField
    name = models.CharField(
        max_length=255, blank=False, null=False, unique=True, default="", 
    )

class OrderLine(TimeStampedModel, TenantModel):
    id = models.AutoField
    order = models.ForeignKey(
        Order,
        blank=False,
        null=False,
        on_delete=models.CASCADE,
        related_name="orderlines",
    )

    product = models.ForeignKey(
        Product, blank=False, null=False, on_delete=models.CASCADE, 
    )
    unit_price = models.DecimalField(
        blank=False,
        null=False,
        max_digits=8,
        decimal_places=2,
    )
    quantity = models.IntegerField(blank=False, null=False, unique=False)
    paid_amount = models.DecimalField(
        blank=False,
        null=False,
        max_digits=8,
        decimal_places=2,
    )
    discount_amount = models.DecimalField(
        blank=False,
        null=False,
        max_digits=8,
        decimal_places=2,
        help_text=_("Amount of the discount applied to the product"),
    )


class Product(TenantModel):
    id = models.AutoField
    objects = ProductManager()
    display_name = models.CharField(max_length=255, blank=False, null=False, unique=False)

我考虑只在最后使用,但我不能在订单线上使用它,因为它是一对多的关系。我被困在如何提高性能上。非常感谢。

标签: pythondjangodatabasedjango-queryset

解决方案


可以使用.only(…)[Django-doc],但是您需要将外键添加到模型本身,否则这将导致另一个N+1 问题:每次都必须进行查询以确定Order它属于什么,所以:

orderlines = OrderLine.objects.select_related('product').only(
    'product__display_name',
    #             add ForeignKey to the Order object ↓
    'quantity', 'paid_amount', 'discount_amount', 'order'
)

return Order.objects.prefetch_related(
    Prefetch('orderlines', queryset=orderlines, to_attr='orderlines_list')
).select_related(
    'delivery_channel'
    'restaurant'
)

推荐阅读