改进 Django 查询集性能,当使用注释存在



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

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

模型表示,RelatedModelModelAnotherModel之间是多对多

Model:
id
field1
field2
field3
RelatedModel:
foreign_key_model (Model)
foreign_key_another (AnotherModel)

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

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

如果唯一需要的是更改 True/False 在 xlsx 中的显示方式 - 一种选择是只有一个related_existsBooleanField 注释,然后自定义在创建 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;

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

首先,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列,让我们用 CASE 语句和 1 存在子查询related_column留下。

Django 来了 - 我们不能(直到 3.0(在不先注释它们的情况下在过滤器中使用表达式。

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


好消息!

从 Django 3.0 开始,我们可以使用直接在 QuerySet 过滤器中输出 BooleanField 的表达式,而无需先进行注释Exists就是这样的布尔字段表达式之一。

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

并且只有一个嵌套选择和一个带注释的字段。


姜戈 2.1, 2.2

这是最终允许布尔表达式的提交,尽管之前添加了许多先决条件。其中之一是表达式对象上存在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检查不适合,原因如下:

  • 它执行LEFT OUTER JOIN- 效率低于EXISTS
  • 它执行LEFT OUTER JOIN- 它连接表,这使得它仅适用于 filter(( 条件(不在注释中 - 何时(,并且仅适用于一对一或一对一对多(一个在相关模型端(关系

您可以大大简化查询,以便:

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

相关内容

  • 没有找到相关文章

最新更新