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

What's the difference between select_related and prefetch_related in Django ORM?

在 Django 文档中,

select_related() "follows" foreign-key relationships, selecting additional related-object data when it executes its query.

prefetch_related() does a separate lookup for each relationship, and does the "joining" in Python.

“加入 python”是什么意思?有人可以举例说明吗?

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

你的理解基本正确。当您要 selecting 的对象是单个对象时,您使用 select_related,因此 OneToOneFieldForeignKey。当你要得到 "set" 的东西时,你使用 prefetch_related,所以 ManyToManyFields 如你所说或相反 ForeignKeys。只是为了阐明我所说的 "reverse 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 服务器获取结果作为 table 的一部分。另一方面,prefetch_related 执行另一个查询,因此减少了原始对象中的冗余列(上例中的 ModelA)。您可以使用 prefetch_related 来表示您可以使用 select_related 的任何内容。

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

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

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

使用这两种方法中的任何一种的唯一原因是当单个大型查询比许多小型查询更可取时。 Django 使用大查询抢占式地在内存中创建模型,而不是对数据库执行按需查询。

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

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

"join in python" prefetch_related 比它应该的更令人担忧。它为每个要加入的 table 创建一个单独的查询。它使用 WHERE IN 子句过滤每个 table,例如:

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

每个 table 被拆分成一个单独的查询,而不是执行一个可能有太多行的连接。

查看已发布的答案。只是觉得如果我加上一个实际例子的答案会更好。

假设您有 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,它只是 return 仅 1 记录任何 M2 对象。同样的事情也适用于 OneToOneField

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

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

步骤:

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

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

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

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