【问题标题】:How to optimize lazy loading of related object, if we already have its instance?如果我们已经有它的实例,如何优化相关对象的延迟加载?
【发布时间】:2019-11-21 15:43:00
【问题描述】:

我喜欢 Django ORM 在查询集中延迟加载相关对象的方式,但我想它是非常不可预测的。 查询集 API 不会在相关对象用于创建查询集时保留它们,从而在以后访问时再次获取它们。

假设我有一个 ModelA 实例(例如 instance_a),它是 的 N 个实例的外键(例如 for_a) >模型B。现在我想对具有给定 ModelA 实例作为外键的 ModelB 执行查询。

Django ORM 提供了两种方式:

  • ModelB 上使用 .filter()
b_qs = ModelB.objects.filter(for_a=instance_a)
for instance_b in b_qs:
    instance_b.for_a # <-- fetches the same row for ModelA again

在此处产生 1 + N 个查询。

  • ModelA 实例上使用反向关系:
b_qs = instance_a.for_a_set.all()
for instance_b in b_qs:
    instance_b.for_a # <-- this uses the instance_a from memory

仅在此处产生 1 个查询。

虽然可以使用第二种方式来实现结果,但它不是标准 API 的一部分,也不适用于所有场景。例如,如果我有 ModelB 的 2 个外键实例(例如,ModelAModelC),并且我想获取两者的相关对象其中。 类似以下的工作:

ModelB.objects.filter(for_a=instance_a, for_c=instance_c)

我想在这种情况下可以使用.intersection(),但我想要一种通过标准 API 实现此目的的方法。毕竟,覆盖这些情况需要更多的非标准查询集函数的代码,这对下一个开发人员可能没有意义。

那么,第一个问题,是否可以使用标准 API 本身优化此类场景? 第二个问题,如果现在不可以,能否通过 QuerySet 进行一些调整?

PS:这是我第一次在这里提问,如有错误请见谅。

【问题讨论】:

    标签: python django foreign-keys django-queryset django-orm


    【解决方案1】:

    您可以使用select_related() 改进查询:

    b_qs = ModelB.objects.select_related('for_a').filter(for_a=instance_a)
    

    b_qs = instance_a.for_a_set.select_related('for_a')
    

    这有帮助吗?

    【讨论】:

    • 这将对该 SQL 查询执行连接,使其变得比需要的更复杂。毕竟,我在内存中已经有了 instance_a ,甚至在构造查询集时也使用了它;再次通过 SQL 获取数据是没有意义的。
    • @shivanshs9 如果你已经有instance_a,那么就不要访问instance_b.for_a,所以不会进行新的查询。
    • @shivanshs9:Django ORM 不像 SqlAlchemy,它确实对现有加载的对象有一些逻辑(这也有一些关于可变性的问题)。
    • @shivanshs9:此外,您还可以使用for_a=id_of_instancea。源自查询集的对象并不真正知道它们的来源,因此它们不会“使用”该信息。这确实可以节省一些查询,但它会使生成查询和解释结果变得更加昂贵。
    • @Ralf 实际上,这是我的一些 DRF 序列化器中的一个实际用例。我在 API 视图中获取 instance_a 以检查权限,然后序列化一些需要再次使用 instance_a 的相关对象。
    【解决方案2】:

    您使用.select_related(..) [Django-doc] 表示ForeignKeys,或使用.prefetch_related(..) [Django-doc] 表示某物对多的关系。

    使用.select_related(..),您将在数据库端创建LEFT OUTER JOIN,并获取两个对象的记录,从而对适当的对象进行反序列化。

    ModelB.objects.<b>select_related('for_a')</b>.filter(for_a=instance_a)

    对于一对多的关系(因此反向的ForeignKey)或ManyToManyFields,这不是一个好主意,因为它可能会导致检索到大量重复的对象。这将导致来自数据库的大量答案,以及 Python 端的大量工作来反序列化这些对象。 .prefetch_related 将进行个别查询,然后自行进行链接。

    【讨论】:

      猜你喜欢
      • 2018-02-09
      • 2015-09-21
      • 1970-01-01
      • 1970-01-01
      • 2012-05-23
      • 1970-01-01
      • 1970-01-01
      • 2012-07-25
      • 2013-10-22
      相关资源
      最近更新 更多