【问题标题】:How can I improve query performance in Django, N + 1 issue?如何提高 Django,N + 1 问题中的查询性能?
【发布时间】:2021-04-23 11:02:01
【问题描述】:

我遇到了 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) 向我展示的那样:

  • 前 5 个查询在我评估查询集时出现(模板中的 for 循环)
  • 当我请求 order_sum() 时,接下来的 100 个查询会出现(模板中的第 2 行) 有了这个,DjDT 告诉我它大约需要 700-800 毫秒(模板中的一些过程但似乎花费的时间并不多,我测试过)。我想把它减少到 500 毫秒。

所以我的问题是:

  • 我可以做些什么来改进?
  • 为什么第三个 SQL 查询重复了 2 次。
  • 有没有办法将最后一个 SQL 查询减少到只有 1 个查询? 我是新手所以请帮助^^。
    #非常感谢您的宝贵时间^^

【问题讨论】:

    标签: python sql django performance sqlite


    【解决方案1】:

    您可以使用 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 docs以获得更多了解

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-08-02
      • 2012-01-28
      • 2017-11-23
      • 2018-11-09
      • 2017-04-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多