首页 > 解决方案 > 如何提高 Django,N + 1 问题中的查询性能?

问题描述

我在 Django 查询的性能方面遇到问题。假设我有 3 个模型,并且 Company 表中有 100 行:

from django.db import models

class Company(models.Model):
    name = models.CharField()

    def order_count(self):
        return self.orders.count()
    def order_sum(self):
        return (self.orders.all().aggregate(models.Sum('total')))['total__sum']

class Customer(models.Model):
    company = models.ForeignKey(Company, related_name="customer", on_delete=models.PROTECT)
    name = models.CharField()
    
    def order_count(self):
        return self.orders.count()

class Order(models.Model):
    company = models.ForeignKey(Company, related_name='orders')
    customer = models.ForeignKey(Customer, related_name="orders")
    value = models.FloatField()

我希望我的模板显示公司名称及其订单总和,然后对于该公司的每个客户,我想显示客户名称及其订单数量。我的查询代码views.py使用这样的预取:

queryset = Company.objects.prefetch_related(
    models.Prefetch('customer',
    queryset=Customer.objects.prefetch_related('orders')), 'orders')

我的伪代码template

for company in queryset:
    print(company.name, company.order_count, company.order_sum)
    for customer in company:
        print(customer.name, customer.order_count)

我检查了 Django 调试工具栏,它需要 105 个查询,使用这些 SQL 语句(伪代码):

SELECT * FROM company
SELECT * FROM customer WHERE customer.company_id IN (100 IDs of the companies)
SELECT * FROM order WHERE order.customer_id IN (the IDs from previous command)(this duplicates 2 times)
SELECT * FROM order WHERE order.company_id IN (100 IDs of the companies)
SELECT SUM(order.value) FROM order WHERE order.company_id = %s (this duplicates 100 times, for each company's id)

正如 Django 调试工具栏 (DjDT) 向我展示的那样:

所以我的问题是:

标签: pythonsqldjangoperformancesqlite

解决方案


您可以使用 annotate 函数在单个查询中获取订单总和。例如

queryset = Company.objects.annotate(
    order_sum=Sum("orders__value")
).prefetch_related(
    models.Prefetch('customer', queryset=Customer.objects.prefetch_related('orders')), 'orders'
)

然后你可以像其他属性一样访问 order_sum 值,使用点运算符

for company in queryset:
    print(company.order_sum)

您可以阅读Django 文档以获得更多理解


推荐阅读