Как я могу улучшить производительность запросов в Django, N 1 проблема?

#python #sql #django #performance #sqlite

#python #sql #django #Производительность #sqlite

Вопрос:

У меня проблемы с производительностью запроса Django. Предположим, у меня есть 3 модели, и у меня есть 100 строк в таблице Company:

 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 в шаблоне)
  • Следующие 100 запросов приходят, когда я запрашиваю order_sum() (строка 2 в шаблоне) При этом DjDT показывает мне, что это занимает около 700-800 мс (некоторые процессы в шаблоне, но, похоже, это занимает не так много времени, я проверял). Я хочу уменьшить его до 500 мс.

Итак, мои вопросы:

  • Что я мог бы сделать, чтобы улучшить ситуацию?
  • Почему третий SQL-запрос дублируется 2 раза.
  • Есть ли какой-либо способ сократить последний SQL-запрос до 1 запроса?. Я новичок, поэтому, пожалуйста, помогите ^^.
    #Большое спасибо за ваше время ^^

Ответ №1:

Вы можете использовать функцию аннотирования, чтобы получить сумму заказа в одном запросе. Например

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

Затем вы можете получить доступ к значению order_sum, как и к другим атрибутам, используя оператор dot

 for company in queryset:
    print(company.order_sum)
 

Вы можете прочитать документы Django для большего понимания