ChatGPT解决这个技术问题 Extra ChatGPT

Django ORM 中的 select_related 和 prefetch_related 有什么区别?

在 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

C
CrazyCasta

你的理解大多是正确的。当您要选择的对象是单个对象时,您使用 select_related,因此 OneToOneFieldForeignKey。当您要获得“一组”事物时,您使用 prefetch_related,因此您所说的 ManyToManyField 或反向 ForeignKey。只是为了澄清我所说的“反向ForeignKey”的意思,这里有一个例子:

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 中运行带有 IN 子句的查询,其中包含它需要的所有 ID。如果您说 3-5 个模型使用相同的外键,select_related 几乎肯定会更好。如果您有 100 或 1000 多个模型使用相同的外键,prefetch_related 实际上可能会更好。在这两者之间,您必须进行测试,看看会发生什么。
我会质疑您对与预取相关的“通常没有多大意义”的评论。对于标记为唯一的 FK 字段确实如此,但是在多行具有相同 FK 值(作者、用户、类别、城市等)的任何地方,预取都会减少 Django 和 DB 之间的带宽,但不会复制行。它通常还使用较少的数据库内存。这些中的任何一个通常都比单个额外查询的开销更重要。鉴于这是一个相当受欢迎的问题的最佳答案,我认为应该在答案中注明。
@GordonWrigley 是的,我写这篇文章已经有一段时间了,所以我回过头来澄清一下。我不确定我是否同意“在数据库上使用更少的内存”这一点,但对一切都是肯定的。它肯定可以在 Python 端使用更少的内存。
c
cdosborn

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

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

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

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

prefetch_related"join in 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);

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


U
Underoos

浏览已经发布的答案。只是认为如果我用实际示例添加答案会更好。

假设您有 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 字段查询 M2 模型及其相关 M1 对象,使用 prefetch_relation 字段查询 M3 对象。

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

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

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

脚步:

找到 m21 对象。查询所有与m21对象相关的ID为1,2,3,4,5的M3对象。对 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 连接比数据库连接便宜。


到目前为止的最佳答案,已经搜索了很多