在Django文件中,
select_related()
“遵循”外键关系,在执行查询时选择其他相关对象数据。
prefetch_related()
对每个关系进行单独的查找,并在Python中执行“联接”。
“在python中进行连接”是什么意思?有人可以举例说明吗?
我的理解是,对于外键关系,使用select_related
; 对于M2M关系,请使用prefetch_related
。它是否正确?
回答 0
您的理解基本上是正确的。您可以使用select_related
时,你将要选择的对象是一个对象,所以OneToOneField
还是ForeignKey
。您可以使用prefetch_related
时,你会得到一个东西的“设置”,所以ManyToManyField
S作为你陈述或反向ForeignKey
秒。为了阐明我的意思是“ 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 Server将结果作为表的一部分返回。prefetch_related
另一方面,执行另一个查询,因此减少了原始对象中的冗余列(ModelA
在上面的示例中)。您可以使用prefetch_related
任何可以使用的东西select_related
。
折衷方案是prefetch_related
必须创建并发送ID列表以选择回服务器,这可能需要一段时间。我不确定在事务中是否有很好的方法,但是我的理解是Django总是只发送一个列表并显示SELECT … WHERE PK IN(…,…,…)基本上。在这种情况下,如果预取的数据稀疏(例如,将美国国家对象链接到人们的地址),这可能会很好,但是,如果它们之间的关系更接近一对一,则会浪费大量通信资源。如有疑问,请尝试两者并查看哪种效果更好。
上面讨论的所有内容基本上都与与数据库的通信有关。但是,在Python方面prefetch_related
具有额外的好处,即使用单个对象表示数据库中的每个对象。使用select_related
重复的对象将在Python中为每个“父”对象创建。由于Python中的对象具有相当大的内存开销,因此这也是一个考虑因素。
回答 1
两种方法可以达到相同的目的,从而放弃不必要的数据库查询。但是他们使用不同的方法来提高效率。
使用这两种方法的唯一原因是,当单个大型查询优于许多小型查询时。Django使用大型查询来抢先在内存中创建模型,而不是针对数据库执行按需查询。
select_related
对每个查找执行联接,但将选择范围扩展为包括所有联接表的列。但是,这种方法有一个警告。
联接有可能使查询中的行数相乘。当您通过外键或一对一字段执行联接时,行数不会增加。但是,多对多联接没有此保证。因此,Django限制select_related
了不会意外导致大规模联接的关系。
对于“ join in python”来说prefetch_related
,应该比它还要令人震惊。它为要连接的每个表创建一个单独的查询。它使用WHERE IN子句过滤每个表,例如:
SELECT "credential"."id",
"credential"."uuid",
"credential"."identity_id"
FROM "credential"
WHERE "credential"."identity_id" IN
(84706, 48746, 871441, 84713, 76492, 84621, 51472);
每个表都被拆分成一个单独的查询,而不是执行可能包含太多行的单个联接。
回答 2
如Django文档所述:
prefetch_related()
返回一个QuerySet,该查询集将自动为每个指定的查询分批检索相关对象。
这与select_related具有相似的目的,因为两者均旨在阻止由于访问相关对象而导致的数据库查询泛滥,但是策略却大不相同。
select_related通过创建SQL连接并将相关对象的字段包括在SELECT语句中来工作。因此,select_related在同一数据库查询中获取相关对象。但是,为了避免跨“许多”关系进行联接会产生更大的结果集,select_related仅限于单值关系-外键和一对一关系。
另一方面,prefetch_related对每个关系进行单独的查找,并在Python中进行“联接”。除了select_related支持的外键和一对一关系之外,这还允许它预取多对多和多对一对象,这不能使用select_related完成。它还支持GenericRelation和GenericForeignKey的预取,但是,必须将其限制为同类结果。例如,仅当查询仅限于一个ContentType时,才支持预取GenericForeignKey引用的对象。
有关此的更多信息:https : //docs.djangoproject.com/en/2.2/ref/models/querysets/#prefetch-related
回答 3
仔细阅读已经发布的答案。只是认为如果我添加一个带有实际示例的答案会更好。
假设您有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)
在这里,您可以使用字段和使用字段的对象查询M2
模型及其相关M1
对象。select_relation
M3
prefetch_relation
但是正如我们所提到M1
的关系由M2
是ForeignKey
,它只返回只有1对任何记录M2
对象。同样的事情也适用OneToOneField
。
但是M3
与的关系来自M2
,ManyToManyField
它可能返回任意数量的M1
对象。
考虑这样一种情况:您有2个M2
对象m21
,m22
这些对象具有相同的5个M3
具有ID的关联对象1,2,3,4,5
。当您M3
为每个对象获取关联的M2
对象时,如果使用select related,则它将如何工作。
脚步:
- 查找
m21
对象。 - 查询
M3
与m21
ID为的对象相关的所有对象1,2,3,4,5
。 - 对
m22
对象和所有其他M2
对象重复相同的操作。
因为我们有相同1,2,3,4,5
的ID两个m21
,m22
对象,如果我们使用select_related选项,它会查询数据库两次,这已经获取相同的ID。
相反,如果您使用prefetch_related,则当您尝试获取M2
对象时,它将在查询M2
表时记下对象返回的所有ID(注意:仅这些ID),并且作为最后一步,Django将对M3
表进行查询以及您的M2
对象已返回的所有ID的集合。并M2
使用Python而不是数据库将它们连接到对象。
这样,您M3
只查询一次所有对象,从而提高了性能。