为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

幕布斯7119047 2022-05-24 16:51:21
我有一个返回一些关于电子邮件列表增长的统计数据的视图。涉及的模型有:模型.pyclass 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 对象并获取一些指标:以下方式:视图.pyclass 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,但我认为它可能需要一个复合索引?另外,有没有更好的方法来做到这一点?也许使用聚合?
查看完整描述

1 回答

?
慕姐8265434

TA贡献1813条经验 获得超2个赞

正如您的代码为每个EmailList实例生成 6 个查询一样。对于 100 个实例,至少 600 个查询会减慢速度。


您可以使用SubQuery()表达式和进行优化.values()。


from django.db.models import Count, OuterRef, Subquery


data = (

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

)


查看完整回答
反对 回复 2022-05-24
  • 1 回答
  • 0 关注
  • 103 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信