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
,因此 OneToOneField
或 ForeignKey
。当你要得到 "set" 的东西时,你使用 prefetch_related
,所以 ManyToManyField
s 如你所说或相反 ForeignKey
s。只是为了阐明我所说的 "reverse ForeignKey
s" 的意思,这里有一个例子:
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
对象。
然而正如我们提到的 M1
与 M2
的关系是 ForeignKey
,它只是 return 仅 1 记录任何 M2
对象。同样的事情也适用于 OneToOneField
。
但是 M3
与 M2
的关系是一个 ManyToManyField
,它可能 return 任意数量的 M1
个对象。
考虑这样一种情况,您有 2 个 M2
个对象 m21
、m22
,它们具有相同的 5 个关联的 M3
个对象ID 1,2,3,4,5
。当您为每个 M2
对象获取关联的 M3
对象时,如果您使用 select 相关,这就是它的工作方式。
步骤:
- 找到
m21
个对象。
- 查询ID为
1,2,3,4,5
. 的m21
个对象相关的所有M3
个对象
- 对
m22
对象和所有其他 M2
对象重复相同的操作。
由于我们对 m21
、m22
对象具有相同的 1,2,3,4,5
ID,如果我们使用 select_related 选项,它将查询数据库两次以获得相同的已获取的 ID。
相反,如果您使用 prefetch_related,当您尝试获取 M2
对象时,它会记录您的对象 returned 的所有 ID(注意:只有ID)在查询 M2
table 时作为最后一步,Django 将使用您的 M2
对象已 returned。并使用 Python 而不是数据库将它们加入 M2
对象。
这样您只查询所有 M3
对象一次,这提高了性能,因为 python 连接比数据库连接便宜。
在 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
,因此 OneToOneField
或 ForeignKey
。当你要得到 "set" 的东西时,你使用 prefetch_related
,所以 ManyToManyField
s 如你所说或相反 ForeignKey
s。只是为了阐明我所说的 "reverse ForeignKey
s" 的意思,这里有一个例子:
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
对象。
然而正如我们提到的 M1
与 M2
的关系是 ForeignKey
,它只是 return 仅 1 记录任何 M2
对象。同样的事情也适用于 OneToOneField
。
但是 M3
与 M2
的关系是一个 ManyToManyField
,它可能 return 任意数量的 M1
个对象。
考虑这样一种情况,您有 2 个 M2
个对象 m21
、m22
,它们具有相同的 5 个关联的 M3
个对象ID 1,2,3,4,5
。当您为每个 M2
对象获取关联的 M3
对象时,如果您使用 select 相关,这就是它的工作方式。
步骤:
- 找到
m21
个对象。 - 查询ID为
1,2,3,4,5
. 的 - 对
m22
对象和所有其他M2
对象重复相同的操作。
m21
个对象相关的所有M3
个对象
由于我们对 m21
、m22
对象具有相同的 1,2,3,4,5
ID,如果我们使用 select_related 选项,它将查询数据库两次以获得相同的已获取的 ID。
相反,如果您使用 prefetch_related,当您尝试获取 M2
对象时,它会记录您的对象 returned 的所有 ID(注意:只有ID)在查询 M2
table 时作为最后一步,Django 将使用您的 M2
对象已 returned。并使用 Python 而不是数据库将它们加入 M2
对象。
这样您只查询所有 M3
对象一次,这提高了性能,因为 python 连接比数据库连接便宜。