在Django文档中,
select_related()
“遵循”外键关系,在执行查询时选择其他相关对象数据。
prefetch_related()
为每个关系执行单独的查找,并在Python语言中执行“连接”。
“在python中做连接”是什么意思?有人能举个例子来说明一下吗?
我的理解是,对于外键关系,使用select_related
;对于M2M关系,使用prefetch_related
。这是正确的吗?
发布于 2015-07-06 10:37:52
你的理解基本上是正确的。当要选择的对象是单个对象时,可以使用select_related
,例如OneToOneField
或ForeignKey
。当你要得到一个“集合”的东西时,你可以使用prefetch_related
,所以ManyToManyField
s正如你所说的,或者反向ForeignKey
s。为了澄清我所说的“反向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 join,因此从SQL server中将结果作为表的一部分返回。另一方面,prefetch_related
执行另一个查询,因此减少了原始对象(上面例子中的ModelA
)中的冗余列。可以使用select_related
的任何东西都可以使用prefetch_related
。
权衡的是,prefetch_related
必须创建ID列表并将其发送回服务器进行选择,这可能需要一段时间。我不确定在事务中是否有一个很好的方法来做到这一点,但我的理解是Django总是只发送一个列表,并说SELECT ...其中pk IN (...,...,...)基本上。在这种情况下,如果预取的数据是稀疏的(比方说链接到人们的地址的U.S. State对象),这是非常好的,但是如果它更接近一对一,这会浪费大量的通信。如果有疑问,可以同时尝试这两种方法,看看哪一种性能更好。
上面讨论的所有内容基本上都是关于与数据库的通信。然而,在Python端,prefetch_related
有一个额外的好处,即使用单个对象来表示数据库中的每个对象。使用select_related
,将为每个“父”对象创建重复的对象。由于Python中的对象具有相当大的内存开销,因此这也是一个需要考虑的问题。
发布于 2017-07-28 23:28:45
这两种方法实现了相同的目的,即放弃不必要的db查询。但他们使用不同的方法来提高效率。
使用这两种方法的唯一原因是当单个大型查询比许多小型查询更可取时。Django使用大型查询在内存中抢占地创建模型,而不是对数据库执行按需查询。
select_related
对每个查找执行联接,但扩展了select以包括所有联接表的列。然而,这种方法有一个警告。
联接有可能使查询中的行数成倍增加。当您在外键或一对一字段上执行连接时,行数不会增加。但是,多对多连接没有这种保证。因此,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);
每个表被拆分成一个单独的查询,而不是执行可能有太多行的单个连接。
发布于 2020-05-10 16:01:06
浏览了已经发布的答案。我只是想,如果我加一个实际例子的答案会更好。
假设你有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
,它只返回任何M2
对象的1记录,同样的情况也适用于OneToOneField
。
但是来自M2
的M3
关系是一个ManyToManyField
,它可以返回任意数量的M1
对象。
考虑这样一种情况,其中有2个M2
对象m21
,m22
具有相同的5,并将M3
对象与ID 1,2,3,4,5
相关联。当您为每个M2
对象获取关联的M3
对象时,如果您使用select related,那么它的工作方式是这样的。
步骤:
Find object.
1,2,3,4,5
.
m21
M3
与m21
object相关的所有与m22
object和所有其他M2
对象ID相同的M3
对象。因为我们对m21
和m22
对象都有相同的1,2,3,4,5
ID,如果我们使用select_related选项,它将查询DB两次,以获得已经获取的相同ID。
相反,如果您使用prefetch_related,当您尝试获取M2
对象时,它将记录您的对象在查询M2
表时返回的所有is (注意:只记录is),作为最后一步,Django将使用您的M3
对象返回的所有is的集合对M2
表进行查询。并使用Python而不是数据库将它们连接到M2
对象。
这样,您只需查询所有M3
对象一次,这提高了性能,因为python连接比数据库连接更便宜。
https://stackoverflow.com/questions/31237042
复制相似问题