在Django文档中:
select_related ()“遵循”外键关系,在执行查询时选择其他相关对象数据。 prefetch_related()对每个关系执行单独的查找,并在Python中执行“连接”。
“在python中进行连接”是什么意思?谁能举例说明一下吗?
我的理解是,对于外键关系,使用select_related;对于M2M关系,使用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字段查询M2模型及其相对M1对象,使用prefetch_relation字段查询M3对象。
然而,正如我们所提到的,M1与M2的关系是一个ForeignKey,对于任何M2对象,它只返回1条记录。同样的事情也适用于OneToOneField。
但是M3与M2的关系是一个多对多字段,它可以返回任意数量的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对象的id都是相同的1,2,3,4,5,如果我们使用select_related选项,它将在DB中查询两次已经获取的相同id。
相反,如果你使用prefetch_related,当你试图获取M2对象时,它会在查询M2表时记录下你的对象返回的所有id(注意:只有id),作为最后一步,Django会用你的M2对象返回的所有id集合查询M3表。并使用Python而不是数据库将它们连接到M2对象。
通过这种方式,您只查询一次所有M3对象,这提高了性能,因为python连接比数据库连接更便宜。
其他回答
不要困惑 **select_related:**用于ForeignKey关系, **prefetch_related:**用于多对多字段关系或反向ForeignKey。 他们做同样的事情减少查询的数量 例如:
class ExampleClassA(models.Model):
title = models.CharField(max_length=50)
class ExampleClassB(models.Model):
example_class_a = models.ForeignKey(ExampleClassA,
on_delete=models.CASCADE)
objects = ExampleClassB.objects.all()
for obj in objects:
print(obj.example_class_a.title)
查询数量(访问相关字段):N+1 (# N是ExampleClassA对象的数量) 如果我们使用这个查询:
objects = ExampleClassB.objects.select_related('example_class_a').all()
查询的个数只有一个。
你的理解基本正确。当你要选择的对象是单个对象时,你要使用select_related,也就是OneToOneField或ForeignKey。你使用prefetch_related当你要得到一个“集合”的东西,所以ManyToManyFields如你所述或反向ForeignKeys。为了澄清我所说的“反向ForeignKeys”,这里有一个例子:
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的东西。
The tradeoffs are that prefetch_related has to create and send a list of IDs to select back to the server, this can take a while. I'm not sure if there's a nice way of doing this in a transaction, but my understanding is that Django always just sends a list and says SELECT ... WHERE pk IN (...,...,...) basically. In this case if the prefetched data is sparse (let's say U.S. State objects linked to people's addresses) this can be very good, however if it's closer to one-to-one, this can waste a lot of communications. If in doubt, try both and see which performs better.
上面讨论的所有内容基本上都是关于与数据库的通信。然而,在Python方面,prefetch_related有一个额外的好处,即使用一个对象来表示数据库中的每个对象。使用select_,将在Python中为每个“父”对象创建相关的重复对象。由于Python中的对象有相当大的内存开销,这也是一个需要考虑的问题。
这两种方法都达到了相同的目的,即放弃不必要的db查询。但是他们使用不同的方法来提高效率。
使用这两种方法的唯一原因是单个大型查询比许多小型查询更可取。Django使用大查询在内存中预先创建模型,而不是对数据库执行按需查询。
Select_related对每个查找执行一个连接,但扩展select以包括所有连接表的列。然而,这种方法有一个警告。
连接有可能增加查询中的行数。当您通过外键或一对一字段执行连接时,行数不会增加。然而,多对多连接没有这种保证。因此,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);
每个表都被分割成一个单独的查询,而不是执行一个可能有太多行的单一连接。
让我试着向你展示Django如何在select_related和prefetch_related中调用db
class a(models.Model):
name = models.CharField(max_length=100)
class b(models.Model):
name = models.CharField(max_length=100)
a = models.ForeignKey(A, on_delete=models.CASCADE)
# select_related查询- >
b.objects.select_related('a').first()
为此执行的SQL查询将为
SELECT * FROM "b" LEFT OUTER JOIN "a" ON ("b"."a_id" = "a"."id") LIMIT 1
这里,Django将使用JOIN获取“一个”模型细节
# prefetch_related查询- >
B.objects.prefetch_related('a').first()
为此执行的SQL查询将为
SELECT * FROM "b" LIMIT 1
SELECT * FROM "a" WHERE "a"."id" IN (ids collected from above query)
这里Django将执行两个SQL查询并通过python将它们合并
浏览了已经公布的答案。只是觉得如果我加上一个实际的例子会更好。
假设你有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的关系是一个多对多字段,它可以返回任意数量的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对象的id都是相同的1,2,3,4,5,如果我们使用select_related选项,它将在DB中查询两次已经获取的相同id。
相反,如果你使用prefetch_related,当你试图获取M2对象时,它会在查询M2表时记录下你的对象返回的所有id(注意:只有id),作为最后一步,Django会用你的M2对象返回的所有id集合查询M3表。并使用Python而不是数据库将它们连接到M2对象。
通过这种方式,您只查询一次所有M3对象,这提高了性能,因为python连接比数据库连接更便宜。