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

khbbv19g  于 2022-12-24  发布在  Go
关注(0)|答案(5)|浏览(187)

在Django文档中,
select_related()“遵循”外键关系,在执行查询时选择其他相关对象数据。
prefetch_related()为每个关系执行单独的查找,并在Python中执行“连接”。
“在python中进行连接”是什么意思?有人能用一个例子来说明吗?
我的理解是,对于外键关系,使用select_related; M2M关系使用prefetch_related,对吗?

f0brbegy

f0brbegy1#

你的理解基本上是正确的。当你要选择的对象是一个单独的对象时,你使用select_related,比如OneToOneFieldForeignKey。当你要得到一个“集合”时,你使用prefetch_related。因此ManyToManyField s如您所述,或者反转ForeignKey s。为了阐明“反转ForeignKey s”的含义,下面是一个示例:

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中的对象有相当多的内存开销,这也是一个考虑因素。

bq3bfh9z

bq3bfh9z2#

这两种方法都达到了相同的目的,即放弃不必要的数据库查询。但它们使用不同的方法来提高效率。
使用这两种方法的唯一原因是当单个大查询比许多小查询更可取时,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);

每个表被拆分为一个单独的查询,而不是对可能过多的行执行单个联接。

kq0g1dla

kq0g1dla3#

浏览了一下已经发布的答案。只是觉得如果我加上一个有实际例子的答案会更好。
假设你有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对象。
然而,正如我们提到的M1M2的关系是ForeignKey,对于任何M2对象,它只返回1条记录,同样的事情也适用于OneToOneField
但是M3M2的关系是ManyToManyField,它可以返回任意数量的M1对象。
假设有两个M2对象m21m22,它们有相同的5关联的M3对象,这些对象的ID为1,2,3,4,5,当你为每个M2对象获取关联的M3对象时,如果你使用select related,这就是它的工作方式。

    • 步骤:**

1.找到m21物体。
1.查询与m21对象关联的所有标识为1,2,3,4,5M3对象。
1.对m22对象和所有其他M2对象重复相同的操作。
由于m21m22对象具有相同的1,2,3,4,5 ID,如果使用select_related选项,则将针对已提取的相同ID查询DB两次。
相反,如果使用prefetch_related,当您尝试获取M2对象时,它将记录对象返回的所有ID(注意:只有ID),作为最后一步,Django将使用M2对象返回的所有ID的集合查询M3表,并使用Python而不是数据库将它们连接到M2对象。
这样你只需要查询所有的M3对象一次,这样可以提高性能,因为python连接比数据库连接便宜。

7ivaypg9

7ivaypg94#

让我来演示一下Django是如何在select_related和prefetch_related中调用数据库的

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_相关查询-〉

b.objects.select_related('a').first()

为此执行的SQL查询将为

SELECT * FROM "b" LEFT OUTER JOIN "a" ON ("b"."a_id" = "a"."id") LIMIT 1

在这里Django将使用JOIN获得“a”模型细节

预取相关查询-〉

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合并它们

mlmc2os5

mlmc2os55#

你不要误会

    • select_related:**用于外键关系,
    • prefetch_related:**用于多对多字段关系或反向外键。
      他们做同样的事情来减少查询的数量
      例如:
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是示例类A的对象编号)
如果我们使用这个查询:

objects = ExampleClassB.objects.select_related('example_class_a').all()

查询次数只能为一次。

相关问题