【问题标题】:What's the difference between select_related and prefetch_related in Django ORM?Django ORM 中的 select_related 和 prefetch_related 有什么区别?
【发布时间】:2015-09-23 01:55:22
【问题描述】:

在 Django 文档中,

select_related()“遵循”外键关系,在执行查询时选择其他相关对象数据。

prefetch_related() 对每个关系进行单独的查找,并在 Python 中“加入”。

“在python中加入”是什么意思?谁能举例说明一下?

我的理解是,对于外键关系,使用select_related;对于 M2M 关系,请使用 prefetch_related。这是正确的吗?

【问题讨论】:

  • 在python中执行join意味着不会在数据库中发生join。使用 select_related,您的连接发生在数据库中,您只需要进行一次数据库查询。使用 prefetch_related,您将执行两个查询,然后结果将由 ORM“连接”,因此您仍然可以键入 object.related_set
  • 作为脚注,Timmy O'Mahony 还可以使用数据库命中来解释它们的差异:link

标签: python django django-models django-orm


【解决方案1】:

浏览已发布的答案。只是觉得如果我添加一个带有实际示例的答案会更好。

假设你有 3 个相关的 Django 模型。

class M1(models.Model):
    name = models.CharField(max_length=10)

class M2(models.Model):
    name = models.CharField(max_length=10)
    select_relation = models.ForeignKey(M1, on_delete=models.CASCADE)
    prefetch_relation = models.ManyToManyField(to='M3')

class M3(models.Model):
    name = models.CharField(max_length=10)

在这里您可以使用select_relation 字段和M3 对象使用prefetch_relation 字段查询M2 模型及其相关M1 对象。

然而,正如我们提到的,M1M2 的关系是 ForeignKey,它只返回任何 M2 对象的 1 记录。同样的事情也适用于OneToOneField

M3M2 的关系是ManyToManyField,它可能返回任意数量的M1 对象。

假设您有 2 个M2 对象m21m22,它们具有相同的5 关联M3 对象,ID 为1,2,3,4,5。当您为每个 M2 对象获取关联的 M3 对象时,如果您使用 select related,这就是它的工作方式。

步骤:

  1. 找到m21对象。
  2. 查询ID为1,2,3,4,5m21对象的所有M3对象。
  3. m22 对象和所有其他 M2 对象重复相同的操作。

由于m21m22 对象的1,2,3,4,5 ID 相同,因此如果我们使用 select_related 选项,它将两次查询数据库以获取已获取的相同 ID。

相反,如果您使用 prefetch_related,当您尝试获取 M2 对象时,它会在查询 M2 表时记录您的对象返回的所有 ID(注意:仅 ID),作为最后一步, Django 将使用您的M2 对象返回的所有ID 集对M3 表进行查询。并使用 Python 而不是数据库将它们加入M2 对象。

这样,您只需查询一次所有 M3 对象,这会提高性能,因为 python 连接比数据库连接便宜。

【讨论】:

    【解决方案2】:

    您的理解大部分是正确的。当您要选择的对象是单个对象时,您可以使用select_related,因此使用OneToOneFieldForeignKey。当您要获得一组“东西”时,您使用prefetch_related,因此如您所说的ManyToManyFields 或反向ForeignKeys。只是为了澄清我所说的“反向ForeignKeys”的意思,这里有一个例子:

    class ModelA(models.Model):
        pass
    
    class ModelB(models.Model):
        a = ForeignKey(ModelA)
    
    ModelB.objects.select_related('a').all() # Forward ForeignKey relationship
    ModelA.objects.prefetch_related('modelb_set').all() # Reverse ForeignKey relationship
    

    不同之处在于select_related 执行SQL 连接,因此从SQL 服务器将结果作为表的一部分返回。另一方面,prefetch_related 执行另一个查询,因此减少了原始对象中的冗余列(上例中的ModelA)。您可以将prefetch_related 用于您可以使用select_related 的任何内容。

    权衡是prefetch_related 必须创建一个 ID 列表并将其发送回服务器,这可能需要一段时间。我不确定在事务中是否有这样做的好方法,但我的理解是 Django 总是只发送一个列表并说 SELECT ... WHERE pk IN (...,...,...)基本上。在这种情况下,如果预取的数据是稀疏的(比如说美国国家对象链接到人们的地址),这可能非常好,但是如果它更接近一对一,这可能会浪费大量的通信。如有疑问,请尝试两者,看看哪个效果更好。

    上面讨论的一切基本上都是关于与数据库的通信。然而,在 Python 方面,prefetch_related 具有额外的好处,即使用单个对象来表示数据库中的每个对象。使用select_related,将为每个“父”对象在 Python 中创建重复对象。由于 Python 中的对象有相当多的内存开销,这也是一个考虑因素。

    【讨论】:

    • 什么更快?
    • select_related 是一个查询,而prefetch_related 是两个,所以前者更快。但是select_related 不会为ManyToManyField 提供帮助
    • @eladsilver 抱歉回复缓慢。这实际上取决于。 select_related 在 SQL 中使用 JOIN,而 prefetch_related 在第一个模型上运行查询,收集它需要预取的所有 ID,然后在 WHERE 中运行带有所需 ID 的 IN 子句的查询。如果你说 3-5 个模型使用相同的外键,select_related 几乎肯定会更好。如果您有 100 或 1000 多个模型使用相同的外键,prefetch_related 实际上可能会更好。在这两者之间,您必须进行测试,看看会发生什么。
    • 我会质疑您关于预取相关“通常没有多大意义”的评论。对于标记为唯一的 FK 字段确实如此,但是在多行具有相同 FK 值(作者、用户、类别、城市等)的任何地方,预取都会减少 Django 和 DB 之间的带宽,但不会复制行。它通常还使用较少的数据库内存。这些中的任何一个通常都比单个额外查询的开销更重要。鉴于这是一个相当受欢迎的问题的最佳答案,我认为应该在答案中注明。
    • @GordonWrigley 是的,我已经有一段时间没有写了,所以我回过头来澄清一下。我不确定我是否同意“在数据库上使用更少的内存”这一点,但对一切都是肯定的。而且它肯定可以在 Python 端使用更少的内存。
    【解决方案3】:

    这两种方法都达到了相同的目的,即放弃不必要的数据库查询。但他们使用不同的方法来提高效率。

    使用这两种方法的唯一原因是单个大型查询优于许多小型查询。 Django 使用大查询在内存中抢先创建模型,而不是对数据库执行按需查询。

    select_related 对每次查找执行连接,但扩展选择以包括所有连接表的列。但是,这种方法有一个警告。

    联接有可能使查询中的行数成倍增加。当您对外键或一对一字段执行连接时,行数不会增加。但是,多对多连接没有这种保证。因此,Django 将select_related 限制为不会意外导致大规模连接的关系。

    prefetch_related“加入 python” 比它应该的更令人担忧。它为要连接的每个表创建一个单独的查询。它使用 WHERE IN 子句过滤这些表中的每一个,例如:

    SELECT "credential"."id",
           "credential"."uuid",
           "credential"."identity_id"
    FROM   "credential"
    WHERE  "credential"."identity_id" IN
        (84706, 48746, 871441, 84713, 76492, 84621, 51472);
    

    不是对可能有太多行执行单个连接,而是将每个表拆分为一个单独的查询。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2023-01-06
      • 2021-09-06
      • 2018-06-25
      • 2020-03-21
      • 2017-03-23
      • 2018-09-05
      • 2012-08-29
      相关资源
      最近更新 更多