简体   繁体   中英

How can I improve query performance in Django, N + 1 issue?

I'm having problems with the performance of Django query. Assume I have 3 models and I have 100 rows in Company table:

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

I want my template to show company's name and sum of its orders, then for each customer of this company, I want to show the customer name with the number of their orders. My query code in views.py is using prefetch like this:

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

My pseudocode for template :

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

I've checked with Django Debug Toolbar, it takes 105 queries, with these SQL sentence (pseudo code):

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)

As Django Debug Toolbar (DjDT) shows me:

  • The first 5 Queries come when I evaluate the queryset (for loop in the template)
  • The next 100 queries come when I request order_sum() (line 2 in the template) With this, DjDT show me that it takes about 700-800ms (some processes in the template but it seems to take not much time, I tested). I want to reduce it to 500ms.

So my questions are:

  • What I could do to improve?
  • Why the third SQL Query duplicates 2 times.
  • Is there any way to reduce the last SQL query to just 1 query?. I'm a newbie so please help ^^.
    #Thanks for much for your time ^^

You could use annotate function to get order sum in a single query. For example

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

Then you can access order_sum value like others attribute, using dot operator

for company in queryset:
    print(company.order_sum)

You can read the Django docs for more understanding

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM