首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >在Django ORM中,select_related和prefetch_related有什么不同?

在Django ORM中,select_related和prefetch_related有什么不同?
EN

Stack Overflow用户
提问于 2015-07-06 10:31:41
回答 3查看 196.8K关注 0票数 394

在Django文档中,

select_related()“遵循”外键关系,在执行查询时选择其他相关对象数据。

prefetch_related()为每个关系执行单独的查找,并在Python语言中执行“连接”。

“在python中做连接”是什么意思?有人能举个例子来说明一下吗?

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

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2015-07-06 10:37:52

你的理解基本上是正确的。当要选择的对象是单个对象时,可以使用select_related,例如OneToOneFieldForeignKey。当你要得到一个“集合”的东西时,你可以使用prefetch_related,所以ManyToManyFields正如你所说的,或者反向ForeignKeys。为了澄清我所说的“反向ForeignKeys”是什么意思,这里有一个例子:

代码语言:javascript
复制
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中的对象具有相当大的内存开销,因此这也是一个需要考虑的问题。

票数 545
EN

Stack Overflow用户

发布于 2017-07-28 23:28:45

这两种方法实现了相同的目的,即放弃不必要的db查询。但他们使用不同的方法来提高效率。

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

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

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

prefetch_related的“在python中加入”比它应该的要更令人担忧。它为每个要联接的表创建一个单独的查询。它使用WHERE IN子句过滤每个表,如下所示:

代码语言:javascript
复制
SELECT "credential"."id",
       "credential"."uuid",
       "credential"."identity_id"
FROM   "credential"
WHERE  "credential"."identity_id" IN
    (84706, 48746, 871441, 84713, 76492, 84621, 51472);

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

票数 29
EN

Stack Overflow用户

发布于 2020-05-10 16:01:06

浏览了已经发布的答案。我只是想,如果我加一个实际例子的答案会更好。

假设你有3个相关的Django模型。

代码语言:javascript
复制
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

但是来自M2M3关系是一个ManyToManyField,它可以返回任意数量的M1对象。

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

步骤:

Find object.

  • Query 1,2,3,4,5.

  • Repeat
  1. m21 M3m21 object相关的所有与m22 object和所有其他M2对象ID相同的M3对象。

因为我们对m21m22对象都有相同的1,2,3,4,5 ID,如果我们使用select_related选项,它将查询DB两次,以获得已经获取的相同ID。

相反,如果您使用prefetch_related,当您尝试获取M2对象时,它将记录您的对象在查询M2表时返回的所有is (注意:只记录is),作为最后一步,Django将使用您的M3对象返回的所有is的集合对M2表进行查询。并使用Python而不是数据库将它们连接到M2对象。

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

票数 11
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/31237042

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档