在 Django ORM 中,select_related 和 prefetch_related 有什么区别?
在 Django ORM 中,select_related 和 prefetch_related 有什么区别?
在 Django 文档中:
select_related()
\"跟踪\" 外键关系,在执行查询时选择额外的关联数据。
prefetch_related()
为每个关系执行单独的查找,并在 Python 中进行 \"连接\"。
什么意思是 \"在 Python 中进行连接\"?有人能举个例子吗?
我的理解是,对于外键关系,使用 select_related
;对于 M2M 关系,使用 prefetch_related
。这正确吗?
浏览了已发布的答案。只是想如果我添加一个具体实例的答案会更好。
假设您有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
字段和prefetch_relation
字段查询M2
模型及其相关的M1
对象和M3
对象。
但是因为我们已经提到M2
从M1
的关系是ForeignKey
,它只返回任何M2
对象的1条记录。同样的事情也适用于OneToOneField
。
但是M3
从M2
的关系是ManyToManyField
,它可以返回任何数量的M1
对象。
考虑这样一种情况,您有2个M2
对象m21
,m22
,它们具有相同的M3
对象,其ID为1,2,3,4,5
。当您为每个M2
对象获取关联的M3
对象时,如果使用选择相关功能,它将如何工作。
步骤:
- 找到
m21
对象。 - 查询所有与
m21
对象相关的M3
对象,其ID为1,2,3,4,5
。 - 对
m22
对象和所有其他M2
对象执行相同的操作。
由于对于m21
,m22
对象具有相同的1,2,3,4,5
ID,因此如果我们使用select_related选项,则会查询两次DB,而这些ID已经被获取。
相比之下,如果您使用prefetch_related,则在尝试获取M2
对象时,它将在查询M2
表时记录对象返回的所有ID(注意:仅为ID),最后一步,Django将使用您的M2
对象中返回的所有ID集合向M3
表进行查询,并使用 Python 而不是数据库将它们与M2
对象连接。
这样,您只查询所有M3
对象一次,可以提高性能,因为 Python 连接比数据库连接更便宜。
你的理解大部分是正确的。当你要选择的对象是单个对象时,如OneToOneField
或ForeignKey
时,你使用select_related
。当你要获取一组东西时,如你所述的ManyToManyField
或反向ForeignKey
时,你使用prefetch_related
。只是为了澄清我在这里所说的“反向ForeignKey
”是一个例子:
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服务器表的一部分返回结果。另一方面,prefetch_related
执行另一个查询,因此减少原始对象(上面示例中的ModelA
)中的冗余列。您可以使用任何可以使用select_related
的prefetch_related
。
最大的权衡是prefetch_related
必须创建并发送要选择回服务器的ID列表,这可能需要一些时间。我不确定是否有一种好的方法在交易中执行此操作,但我的理解是,Django总是只发送一个列表并说SELECT…WHERE pk IN (…,…,…) 基本上。如果预取的数据是稀疏的(假设与人们的地址链接的美国州对象),这将非常好,但如果接近一对一,这将浪费大量通信。如果有疑问,请尝试两种方法,看哪一种表现更好。
上面讨论的所有内容基本上都涉及与数据库的通信。然而,在Python方面,prefetch_related
的额外好处是每个数据库对象使用单个对象来表示。对于使用select_related
,每个“父”对象都将在Python中创建重复的对象。由于Python中的对象具有相当大的内存开销,这也可以考虑。