使用注释存在时,改善Django queryset性能

问题描述 投票:2回答:2

我有一个返回大量数据的查询集,可以按年份对其进行过滤,这将返回约10万行,或者显示所有将带来约100万行的数据。

此注释的目的是生成一个xlsx电子表格。

[模型表示,RelatedModelModelAnotherModel之间是许多个

Model:
    id
    field1
    field2
    field3

RelatedModel:
    foreign_key_model (Model)
    foreign_key_another (AnotherModel)

Queryset,如果存在关系,它将进行注释,此注释非常慢,可能需要几分钟。

Model.objects.all().annotate(
    related_exists=Exists(RelatedModel.objects.filter(foreign_key_model=OuterRef('id'))),
    related_column=Case(
        When(related_exists=True, then=Value('The relation exists!')),
        When(related_exists=False, then=Value('The relation doesn't exist!')),
        default=Value('This is the default value!'),
        output_field=CharField(),
    )
).values_list(
    'related_column',
    'field1',
    'field2',
    'field3'
)
django django-queryset query-performance
2个回答
0
投票

您可以将查询大大简化为:

from django.db.models import Count
Model.objects.all().annotate(
    related_column=Case(
        When(relatedmodel_set__isnull=True, then=Value("The relation doesn't exist!")), 
        default=Value("The relation exists!"), 
        output_field=CharField()
    )
)

relatedmodel_set是外键上的related_name


0
投票

[如果只需要更改xlsx中True / False的显示方式-一种选择是只具有一个related_exists BooleanField批注,然后自定义在创建xlsx文档时如何转换它-即在序列化器中。数据库应存储原始/未格式化的值,然后应用程序准备将其显示给用户。

要考虑的其他事项:

  • 加速过滤的索引。
  • 如果过滤后有数百万条记录,则在一个表中-也许可以考虑对表进行分区。

但是让我们看一下原始查询的原始sql。就像这样:

SELECT [model_fields],
       EXISTS([CLIENT_SELECT]) AS related_exists,
       CASE
       WHEN EXISTS([CLIENT_SELECT]) = true THEN 'The relation exists!'
       WHEN EXISTS([CLIENT_SELECT]) = true THEN 'The relation does not exist!'
       ELSE 'The relation exists!'
       END AS related_column
FROM model;

并且马上我们可以看到存在的客户的嵌套查询 3次。即使完全相同,也可以执行最少2次,最多3次。数据库可能会将其优化为快于3倍,但仍不是1 x的最佳选择。

[首先,EXISTS返回True或False,我们仅需保留一项检查就可以将它设为True,将'The relation does not exist!'设为默认值。

    related_column=Case(
        When(related_exists=True, then=Value('The relation exists!')),
        default=Value('The relation does not exist!')

为什么related_column再次执行相同的选择却不采用related_exists的值?

因为在计算另一列时,我们无法引用计算列]-这是django知道的数据库级约束,并且重复了表达式。

等等,那么我们实际上不需要related_exists列,只需将related_column保留为CASE语句,而1个存在子查询。

Django来了-我们(直到3.0)不能在过滤器中使用表达式而不先对其进行注释。

因此,我们的情况就像这样:为了在Exist中使用When,我们首先需要将其添加为注释,但不会将其用作引用,而是表达式的完整副本。


好消息!

由于Django 3.0,我们可以使用直接在QuerySet过滤器中输出BooleanField的表达式,而无需首先注释

Exists是此类BooleaField表达式之一。
Model.objects.all().annotate(
    related_column=Case(
        When(
            Exists(RelatedModel.objects.filter(foreign_key_model=OuterRef('id'))),
            then=Value('The relation exists!'),
        ),
        default=Value('The relation doesn't exist!'),
        output_field=CharField(),
    )
)

而且只有一个嵌套的select和一个带注释的字段。


Django 2.1,2.2

这里是commit,它最终确定了布尔表达式的允许量,尽管之前为其添加了许多前提条件。其中之一是表达式对象上是否存在conditional属性,并检查该属性。

因此,尽管不推荐

未测试对于Django 2.1、2.2似乎没有什么太大的作用(在没有conditional检查之前,它将需要进行更多的侵入性更改):
  • 创建Exists表达式实例
  • conditional = True猴子修补它
  • When语句中用作条件
related_model_exists = Exists(RelatedModel.objects.filter(foreign_key_model=OuterRef('id')))

setattr(related_model_exists, 'conditional', True)

Model.objects.all().annotate(
    related_column=Case(
        When(
            relate_model_exists,
            then=Value('The relation exists!'),
        ),
        default=Value('The relation doesn't exist!'),
        output_field=CharField(),
    )
)


相关检查

[relatedmodel_set__isnull=True

检查是否适合[[not有多种原因:
    它执行LEFT OUTER JOIN-效率低于EXISTS
  • 它执行LEFT OUTER JOIN-它联接表,这使其仅适用于filter()条件(不适用于注释-时),并且仅适用于OneToOne或OneToMany(一个在relatedmodel一侧)关系]]
© www.soinside.com 2019 - 2024. All rights reserved.