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

在Django doc中,

select_related()“follows"外键关系,在执行查询时选择其他相关对象数据。

prefetch_related()对每个关系进行单独的查找,并执行&;join "在Python中。

“doing the joining in python”是什么意思?谁能举例说明一下吗?

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

260203 次浏览

你的理解基本正确。当你要选择的对象是单个对象时,使用select_related,因此OneToOneFieldForeignKey。当你要获取一个“集合”的东西时,你使用prefetch_related,所以你声明的__abc4或反转__abc2。为了澄清我所说的“reverse 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来做的事情。

代价是prefetch_related必须创建一个id列表并将其发送回服务器,这可能需要一段时间。我不确定在事务中是否有一个很好的方法来做到这一点,但我的理解是Django总是只发送一个列表并说SELECT…WHERE pk IN(…,…,…)在这种情况下,如果预取的数据是稀疏的(假设美国国家对象链接到人们的地址),这可能是非常好的,但是如果它接近一对一,这可能会浪费大量的通信。如果有疑问,两个都试一下,看看哪个性能更好。

上面讨论的所有内容基本上都是关于与数据库的通信。然而,在Python方面,prefetch_related有一个额外的好处,即一个对象被用来表示数据库中的每个对象。使用select_related,将在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);

每个表都被分割成一个单独的查询,而不是执行一个可能有太多行的单一连接。

浏览了已经公布的答案。只是觉得如果我加上一个实际的例子会更好。

假设你有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对象。
  2. 查询与m21对象相关且id为1,2,3,4,5的所有M3对象。
  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而不是database将它们连接到M2对象。

通过这种方式,你只查询一次所有M3对象,这提高了性能,因为python连接比数据库连接更便宜。

不要混淆
**select_related:**用于ForeignKey关系,
**prefetch_related:**用于多对多字段关系或反向ForeignKey 他们做同样的事情减少查询
的数量 例如:< br > < / p >

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.title)

查询数量(访问相关字段):N+1 (# N是ExampleClassA的对象数量)
如果我们使用这个查询:

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


.查询个数只有一个

让我试着向你展示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会得到"a"使用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将它们合并