如何使用prefetch_related减少Django中的SQL查询?

时间:2017-11-08 14:35:41

标签: python django django-models django-templates

我正在尝试优化Django项目(第1.8.6节),其中每个页面一次显示100家公司及其数据。我注意到在下面的index.html代码段中执行了不必要的SQL查询(尤其是contact.get_order_count):

的index.html:

{% for company in company_list %}
    <tr>
        <td>{{ company.name }}</td>
        <td>{{ company.get_order_count }}</td>
        <td>{{ company.get_order_sum|floatformat:2 }}</td>
        <td><input type="checkbox" name="select{{company.pk}}" id=""></td>
    </tr>
    {% for contact in company.contacts.all %}
        <tr>
            <td>&nbsp;</td>
            <td>{{ contact.first_name }} {{ contact.last_name }}</td>
            <td>Orders: {{ contact.get_order_count }}</td>
            <td></td>
        </tr>
    {% endfor %}
{% endfor %}

问题似乎在于使用外键对其他表的常量SQL查询。我查了解如何解决这个问题,并发现prefetch_related()似乎是解决方案。但是,无论我使用什么参数,我都会收到一个关于无法解析预取的TemplateSyntaxError。什么是正确的预取语法,还是有任何其他方法来优化我错过了?

我已经在下面列出了相关的model.py相关摘要,以防它相关。我让prefetch_related使用已定义的方法,但它并没有改变性能或查询量。

model.py:

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

    def get_order_count(self):
        return self.orders.count()
    def get_order_sum(self):
        return self.orders.aggregate(Sum('total'))['total__sum']

class Contact(models.Model):
    company = models.ForeignKey(
        Company, related_name="contacts", on_delete=models.PROTECT)
    first_name = models.CharField(max_length=150)
    last_name = models.CharField(max_length=150, blank=True)

    def get_order_count(self):
        return self.orders.count()

class Order(models.Model):
    company = models.ForeignKey(Company, related_name="orders")
    contact = models.ForeignKey(Contact, related_name="orders")
    total = models.DecimalField(max_digits=18, decimal_places=9)

    def __str__(self):
        return "%s" % self.order_number

编辑: 视图是ListView,并将company_list定义为model = Company。我根据给定的建议修改了视图:

class IndexView(ListView):
    template_name = "mailer/index.html"
    model = Company
    contacts = Contact.objects.annotate(order_count=Count('orders'))
    contact_list = Company.objects.all().prefetch_related(Prefetch('contacts', queryset=contacts))
    paginate_by = 100

2 个答案:

答案 0 :(得分:1)

每次调用方法时,调用get_order_countget_order_sum方法都会导致一个查询。你可以通过annotating the queryset来避免这种情况。

from django.db.models import Count, Sum
contacts = Contact.objects.annotate(order_count=Count('orders'), order_sum=Sum('orders'))

然后,您需要使用Prefetch对象告诉Django使用带注释的查询集。

contact_list = Company.objects.all().prefetch_related(Prefetch("contacts", queryset=contacts)

请注意,您需要在视图中将prefetch_related添加到查询集中,无法在模板中调用它。

由于您使用的是ListView,因此您应该覆盖get_queryset方法,并在那里调用prefetch_related()

class IndexView(ListView):
    template_name = "mailer/index.html"
    model = Company
    paginate_by = 100

    def get_queryset(self):
        # Annotate the contacts with the order counts and sums
        contacts = Contact.objects.annotate(order_count=Count('orders')
        queryset = super(IndexView, self).get_queryset()
        # Annotate the companies with order_count and order_sum
        queryset = queryset.annotate(order_count=Count('orders'), order_sum=Sum('orders'))
        # Prefetch the related contacts. Use the annotated queryset from before
        queryset = queryset.prefetch_related(Prefetch('contacts', queryset=contacts))
        return queryset

然后在您的模板中,您应该使用{{ contact.order_count }}代替{{ contact.get_order_count }}{{ company.order_count }}代替{{ company.get_order_count }}

答案 1 :(得分:0)

在views.py

中试试
company_list = Company.objects.all().prefetch_related("order", "contacts")