在 Django 文档中,
select_related() “遵循”外键关系,在执行查询时选择其他相关对象数据。 prefetch_related() 对每个关系进行单独的查找,并在 Python 中进行“加入”。
“在python中加入”是什么意思?有人可以举例说明吗?
我的理解是,对于外键关系,使用select_related
;对于 M2M 关系,使用 prefetch_related
。这个对吗?
你的理解大多是正确的。当您要选择的对象是单个对象时,您使用 select_related
,因此 OneToOneField
或 ForeignKey
。当您要获得“一组”事物时,您使用 prefetch_related
,因此您所说的 ManyToManyField
或反向 ForeignKey
。只是为了澄清我所说的“反向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 连接,因此从 SQL 服务器获取结果作为表的一部分。另一方面,prefetch_related
执行另一个查询,因此减少了原始对象中的冗余列(上例中的 ModelA
)。您可以将 prefetch_related
用于您可以使用 select_related
的任何内容。
权衡是 prefetch_related
必须创建一个 ID 列表并将其发送回服务器,这可能需要一段时间。我不确定在事务中是否有这样做的好方法,但我的理解是 Django 总是只发送一个列表并说 SELECT ... WHERE pk IN (...,...,...)基本上。在这种情况下,如果预取的数据是稀疏的(假设美国州对象链接到人们的地址),这可能非常好,但是如果它更接近一对一,这可能会浪费大量通信。如果有疑问,请尝试两者,看看哪个表现更好。
上面讨论的一切基本上都是关于与数据库的通信。然而,在 Python 方面,prefetch_related
具有额外的好处,即使用单个对象来表示数据库中的每个对象。使用 select_related
将在 Python 中为每个“父”对象创建重复对象。由于 Python 中的对象有相当多的内存开销,这也是一个考虑因素。
两种方法都达到相同的目的,放弃不必要的数据库查询。但是他们使用不同的方法来提高效率。
使用这两种方法的唯一原因是单个大查询优于许多小查询。 Django 使用大查询在内存中抢先创建模型,而不是对数据库执行按需查询。
select_related
对每次查找执行连接,但扩展选择以包括所有连接表的列。然而,这种方法有一个警告。
连接有可能使查询中的行数成倍增加。当您对外键或一对一字段执行连接时,行数不会增加。但是,多对多连接没有这种保证。因此,Django 将 select_related
限制为不会意外导致大量连接的关系。
prefetch_related
的 "join in 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);
与其对可能有太多行执行单个连接,不如将每个表拆分为一个单独的查询。
浏览已经发布的答案。只是认为如果我用实际示例添加答案会更好。
假设您有 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
。
但是 M3
与 M2
的关系是一个 ManyToManyField
,它可能返回任意数量的 M1
对象。
假设您有 2 个 M2
对象 m21
、m22
,它们具有相同的 5 个关联的 M3
对象,ID 为 1,2,3,4,5
。当您为每个 M2
对象获取关联的 M3
对象时,如果您使用 select related,这就是它的工作方式。
脚步:
找到 m21 对象。查询所有与m21对象相关的ID为1,2,3,4,5的M3对象。对 m22 对象和所有其他 M2 对象重复相同的操作。
由于我们对 m21
、m22
对象具有相同的 1,2,3,4,5
ID,因此如果我们使用 select_related 选项,它将两次查询数据库以获取已获取的相同 ID。
相反,如果您使用 prefetch_related,当您尝试获取 M2
对象时,它会在查询 M2
表时记录您的对象返回的所有 ID(注意:只有 ID),作为最后一步,Django 正在使用您的 M2
对象返回的所有 ID 的集合对 M3
表进行查询。并使用 Python 而不是数据库将它们连接到 M2
对象。
这样,您只需查询所有 M3
对象一次,这会提高性能,因为 python 连接比数据库连接便宜。
不定期副业成功案例分享
select_related
是一个查询,而prefetch_related
是两个查询,所以前者更快。但是select_related
不会为ManyToManyField
提供帮助select_related
在 SQL 中使用 JOIN,而prefetch_related
在第一个模型上运行查询,收集它需要预取的所有 ID,然后在 WHERE 中运行带有 IN 子句的查询,其中包含它需要的所有 ID。如果您说 3-5 个模型使用相同的外键,select_related
几乎肯定会更好。如果您有 100 或 1000 多个模型使用相同的外键,prefetch_related
实际上可能会更好。在这两者之间,您必须进行测试,看看会发生什么。