【问题标题】:Optimise Django subquery (currently using `annotate` and `Count`)优化 Django 子查询(目前使用 `annotate` 和 `Count`)
【发布时间】:2017-10-02 07:52:24
【问题描述】:

使用: Django 1.11、Postgres 9.6

我需要优化 Django ORM 查询以供 Django Rest 框架使用。查询必须返回符合以下条件的记录:

  1. 匹配target 字段中的 ID 列表
  2. 将结果集限制为source ID 在集中出现多次的记录。

当前的方法是使用annotateCount 创建一个子查询,但是添加到分页的每个请求背后的处理量意味着它导致超时或非常缓慢的行为的应用程序。

如果服务器上的 Postgres 有什么可以作为原始查询完成的,我可以接受。

型号:

class Relationship(models.Model):
    id = models.AutoField(primary_key=True)
    source = models.BigIntegerField(db_index=True)
    target = models.BigIntegerField(db_index=True)

查看sn-p:

match_list = [123, 456, 789] # dummy data for example
queryset = Relationship.objects.filter(target__in=match_list)
sub_queryset = (Relationship.objects.filter(target__in=_match_list)
                                    .values('source')
                                    .annotate(source_count=Count("source"))
                                    .filter(source_count__gt=1)
                                    )
sub_ids = [i["source"] for i in sub_queryset]
queryset = (queryset.filter(source__in=sub_ids)
            )

API 将目标 ID 列表作为参数,并以连接到该目标的所有 source ID 列表进行响应。但是,我正在过滤查询集以仅返回连接到两个或多个 targets 的 source 记录。

作为背景,生成的查询集将由 Django Rest Framework 提供服务,它目前正在导致超时,因为请求越多,请求越长

注意:我把它放在 SO 上是因为它会导致我的请求超时并因此导致错误。我知道我可以延长超时时间,但宁愿优化查询。我考虑过 CodeReview,但觉得这更合适。

编辑 1:按照 @albar 的建议,它目前是一个单独的子查询,因为 annotate / Count 操作仅在返回值而不是完整记录时才有效

【问题讨论】:

  • 可以像在目标和源上添加索引一样简单。
  • 谢谢@e4c5,好点,但已经解决了 - 我在两个字段上都有索引(参见模型 - “db_index=True”)所以我相信这是annotate / Count 聚合步骤。
  • 我真的不明白你的查询逻辑,以及为什么有那么多步骤。我认为您想要的查询很简单:Relationship.objects.filter(target__in=_match_list).annotate(source_count=Count("source")).filter(source_count__gt=1).
  • @albar 我最初尝试过这种方法,但注释仅适用于单个记录,因此source_count 值永远不会超过 1。我必须执行 .values 操作才能启用整个结果集的聚合。我认为答案可能在于做一个子查询而不是多个查询,但我在这方面还没有太多经验。
  • @Phil Sheard 好的,我现在明白了。不容易……

标签: python django postgresql


【解决方案1】:

一种可能的解决方案是使用相关子查询:

duplicates = Relationship.objects.exclude(
    id=OuterRef('id')
).filter(source=OuterRef('source'))

Relationship.objects.annotate(
    duplicated=Exists(duplicates)
).filter(
    duplicated=True
)

这样做是建立一个查询集(不能自行评估),它只包含与主查询集的“源”值重复的元素。然后过滤元素,只选择那些元素。

目前在 django 中没有 .annotate(...).filter(...) 是不可能做到这一点的,但是有时这会影响性能:能够仅在数据库中评估它(在 WHERE 子句中)可以大大提高性能。

【讨论】:

  • 这是个好主意,马修 - 感谢您发布它。自从我发布查询以来已经有一段时间了,但我仍在使用代码库,所以我将针对我最终使用的解决方案对其进行测试。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-01-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-04-24
  • 1970-01-01
  • 2021-11-28
相关资源
最近更新 更多