在 Django ORM 中,select_related 和 prefetch_related 有什么区别?

8 浏览
0 Comments

在 Django ORM 中,select_related 和 prefetch_related 有什么区别?

在 Django 文档中:

select_related() \"跟踪\" 外键关系,在执行查询时选择额外的关联数据。

prefetch_related() 为每个关系执行单独的查找,并在 Python 中进行 \"连接\"。

什么意思是 \"在 Python 中进行连接\"?有人能举个例子吗?

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

admin 更改状态以发布 2023年5月24日
0
0 Comments

浏览了已发布的答案。只是想如果我添加一个具体实例的答案会更好。

假设您有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对象。

但是因为我们已经提到M2M1的关系是ForeignKey,它只返回任何M2对象的1条记录。同样的事情也适用于OneToOneField

但是M3M2的关系是ManyToManyField,它可以返回任何数量的M1对象。

考虑这样一种情况,您有2个M2对象m21m22,它们具有相同的M3对象,其ID为1,2,3,4,5。当您为每个M2对象获取关联的M3对象时,如果使用选择相关功能,它将如何工作。

步骤:

  1. 找到m21对象。
  2. 查询所有与m21对象相关的M3对象,其ID为1,2,3,4,5
  3. m22对象和所有其他M2对象执行相同的操作。

由于对于m21m22对象具有相同的1,2,3,4,5 ID,因此如果我们使用select_related选项,则会查询两次DB,而这些ID已经被获取。

相比之下,如果您使用prefetch_related,则在尝试获取M2对象时,它将在查询M2表时记录对象返回的所有ID(注意:仅为ID),最后一步,Django将使用您的M2对象中返回的所有ID集合向M3表进行查询,并使用 Python 而不是数据库将它们与M2对象连接。

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

0
0 Comments

你的理解大部分是正确的。当你要选择的对象是单个对象时,如OneToOneFieldForeignKey时,你使用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_relatedprefetch_related

最大的权衡是prefetch_related必须创建并发送要选择回服务器的ID列表,这可能需要一些时间。我不确定是否有一种好的方法在交易中执行此操作,但我的理解是,Django总是只发送一个列表并说SELECT…WHERE pk IN (…,…,…) 基本上。如果预取的数据是稀疏的(假设与人们的地址链接的美国州对象),这将非常好,但如果接近一对一,这将浪费大量通信。如果有疑问,请尝试两种方法,看哪一种表现更好。

上面讨论的所有内容基本上都涉及与数据库的通信。然而,在Python方面,prefetch_related的额外好处是每个数据库对象使用单个对象来表示。对于使用select_related,每个“父”对象都将在Python中创建重复的对象。由于Python中的对象具有相当大的内存开销,这也可以考虑。

0