使用索引和更好的 SQL 在循环中优化 QuerySet

我有一个返回一些关于电子邮件列表增长的统计数据的视图。涉及的模型有:


模型.py


class Contact(models.Model):

    email_list = models.ForeignKey(EmailList, related_name='contacts')

    customer = models.ForeignKey('Customer', related_name='contacts')

    status = models.CharField(max_length=8)

    create_date = models.DateTimeField(auto_now_add=True)



class EmailList(models.Model):

    customers = models.ManyToManyField('Customer',

        related_name='lists',

        through='Contact')



class Customer(models.Model):

    is_unsubscribed = models.BooleanField(default=False, db_index=True)

    unsubscribe_date = models.DateTimeField(null=True, blank=True, db_index=True)

在视图中,我正在做的是遍历所有 EmailLists 对象并获取一些指标:以下方式:


视图.py


class ListHealthView(View):

    def get(self, request, *args, **kwargs):

        start_date, end_date = get_dates_from_querystring(request)


        data = []

        for email_list in EmailList.objects.all():

            # historic data up to start_date

            past_contacts = email_list.contacts.filter(

                status='active',

                create_date__lt=start_date).count()

            past_unsubscribes = email_list.customers.filter(

                is_unsubscribed=True,

                unsubscribe_date__lt=start_date,

                contacts__status='active').count()

            past_deleted = email_list.contacts.filter(

                status='deleted',

                modify_date__lt=start_date).count()

            # data for the given timeframe

            new_contacts = email_list.contacts.filter(

                status='active',

                create_date__range=(start_date, end_date)).count()


            })

        return Response({'data': data})

现在这工作正常,但随着 My DB 开始增长,此视图的响应时间超过 1 秒,并且偶尔会导致数据库中长时间运行查询。我认为最明显的改进是索引EmailList.customers,但我认为它可能需要一个复合索引?另外,有没有更好的方法来做到这一点?也许使用聚合?


幕布斯7119047
浏览 104回答 1
1回答

慕姐8265434

正如您的代码为每个EmailList实例生成 6 个查询一样。对于 100 个实例,至少 600 个查询会减慢速度。您可以使用SubQuery()表达式和进行优化.values()。from django.db.models import Count, OuterRef, Subquerydata = (    EmailList.objects    .annotate(        past_contacts=Count(Subquery(            Contact.objects.filter(                email_list=OuterRef('pk'),                status='active',                create_date__lt=start_date            ).values('id')        )),        past_unsubscribes=...,        past_deleted=...,        new_contacts=...,        new_unsubscribes=...,        new_deleted=...,    )    .values(        'past_contacts', 'past_unsubscribes',        'past_deleted', 'new_contacts',        'new_unsubscribes', 'new_deleted',    ))更新:对于旧版本的 Django,您的子查询可能需要如下所示customers = (    Customer.objects    .annotate(        template_count=Subquery(            CustomerTemplate.objects            .filter(customer=OuterRef('pk'))            .values('customer')            .annotate(count=Count('*')).values('count')        )    ).values('name', 'template_count'))
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Python