在Django文档中:
select_related ()“遵循”外键关系,在执行查询时选择其他相关对象数据。 prefetch_related()对每个关系执行单独的查找,并在Python中执行“连接”。
“在python中进行连接”是什么意思?谁能举例说明一下吗?
我的理解是,对于外键关系,使用select_related;对于M2M关系,使用prefetch_related。这对吗?
在Django文档中:
select_related ()“遵循”外键关系,在执行查询时选择其他相关对象数据。 prefetch_related()对每个关系执行单独的查找,并在Python中执行“连接”。
“在python中进行连接”是什么意思?谁能举例说明一下吗?
我的理解是,对于外键关系,使用select_related;对于M2M关系,使用prefetch_related。这对吗?
当前回答
让我试着向你展示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将它们合并
其他回答
不要困惑 **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()
查询的个数只有一个。
浏览了已经公布的答案。只是觉得如果我加上一个实际的例子会更好。
假设你有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()可以将多个SELECT查询减少为1个SELECT查询,在一对一关系中使用前向外键和反向外键,在一对多和多对多关系中使用前向外键。 在一对一、一对多和多对多的关系中,prefetch_related()可以使用正向外键和反向外键将多个SELECT查询减少到最少2个SELECT查询。
*你可以看到我的回答解释向前外键和反向外键的含义。
下面展示了我在一对一、一对多和多对多关系中使用正向外键和反向外键对select_related()和prefetch_related()进行的实验。
> <一对一的关系
例如,Person和PersonDetail模型具有如下所示的一对一关系:
class Person(models.Model):
name = models.CharField(max_length=20)
def __str__(self):
return self.name
class PersonDetail(models.Model):
person = models.OneToOneField(Person, on_delete=models.CASCADE)
age = models.IntegerField()
gender = models.CharField(max_length=20)
def __str__(self):
return str(self.age) + " " + self.gender
然后,Person和PersonDetail模型各有5个对象,如下所示:
<前向外键>
然后,我从PersonDetail模型迭代Person模型,如下所示:
for obj in PersonDetail.objects.all():
print(obj.person)
然后,在控制台输出以下内容:
John
Lisa
David
Anna
Kai
然后,运行6个SELECT查询,如下所示。*我使用PostgreSQL,下面是PostgreSQL的查询日志,你可以看到我的回答,解释如何在PostgreSQL上启用和禁用查询日志:
接下来,我用select_related(“Person”)从PersonDetail模型迭代Person模型,如下所示。* select_related()和all()的顺序不重要:
for obj in PersonDetail.objects.select_related("person").all():
print(obj.person)
然后,在控制台输出以下内容:
John
Lisa
David
Anna
Kai
然后,运行1个SELECT查询,如下所示:
接下来,我用prefetch_related(“Person”)从PersonDetail模型迭代Person模型,如下所示。* prefetch_related()和all()的顺序不重要:
for obj in PersonDetail.objects.prefetch_related("person").all():
print(obj.person)
然后,在控制台输出以下内容:
John
Lisa
David
Anna
Kai
然后,运行2个SELECT查询,如下所示:
<反向外键>
接下来,我迭代Person模型中的PersonDetail模型,如下所示:
for obj in Person.objects.all():
print(obj.persondetail)
然后,在控制台输出以下内容:
32 Male
26 Female
18 Male
27 Female
57 Male
然后,运行6个SELECT查询,如下所示:
接下来,我用select_related(" PersonDetail ")迭代Person模型中的PersonDetail模型,如下所示:
for obj in Person.objects.select_related("persondetail").all():
print(obj.persondetail)
然后,在控制台输出以下内容:
32 Male
26 Female
18 Male
27 Female
57 Male
然后,运行1个SELECT查询,如下所示:
接下来,我用prefetch_related(“PersonDetail”)从Person模型迭代PersonDetail模型,如下所示:
for obj in Person.objects.prefetch_related("persondetail").all():
print(obj.persondetail)
然后,在控制台输出以下内容:
32 Male
26 Female
18 Male
27 Female
57 Male
然后,运行2个SELECT查询,如下所示:
> <一对多关系
例如,Category和Product模型是一对多的关系,如下图所示:
class Category(models.Model):
name = models.CharField(max_length=20)
def __str__(self):
return self.name
class Product(models.Model):
category = models.ForeignKey(Category, on_delete=models.CASCADE)
name = models.CharField(max_length=50)
price = models.DecimalField(decimal_places=2, max_digits=5)
def __str__(self):
return str(self.category) + " " + self.name + " " + str(self.price)
那么Category模型有4个对象,Product模型有6个对象,如下图所示:
<前向外键>
然后,我从Product模型迭代Category模型,如下图所示:
for obj in Product.objects.all():
print(obj.category)
然后,在控制台输出以下内容:
Fruits
Fruits
Vegetable
Meat
Meat
Fish
然后,运行7个SELECT查询,如下所示:
接下来,我用select_related(“Category”)从Product模型迭代Category模型,如下所示:
for obj in Product.objects.select_related("category").all():
print(obj.category)
然后,在控制台输出以下内容:
Fruits
Fruits
Vegetable
Meat
Meat
Fish
然后,运行1个SELECT查询,如下所示:
接下来,我用prefetch_related(“Category”)从Product模型迭代Category模型,如下所示:
for obj in Product.objects.prefetch_related("category").all():
print(obj.category)
然后,在控制台输出以下内容:
Fruits
Fruits
Vegetable
Meat
Meat
Fish
然后,运行2个SELECT查询,如下所示:
<反向外键>
接下来,我从Category模型中迭代Product模型,如下所示:
for obj in Category.objects.all():
print(obj.product_set.all())
然后,在控制台输出以下内容:
<QuerySet [<Product: Fruits Apple 10.00>, <Product: Fruits Orange 20.00>]>
<QuerySet [<Product: Vegetable Carrot 30.00>]>
<QuerySet [<Product: Meat Chicken 40.00>, <Product: Meat Beef 50.00>]>
<QuerySet [<Product: Fish Salmon 60.00>]>
然后,运行5个SELECT查询,如下所示:
接下来,我尝试用select_related(“product_set”)从类别模型迭代产品模型,如下所示:
for obj in Category.objects.select_related("product_set").all():
print(obj.product_set.all())
然后,出现以下错误,因为select_related("product_set")不能与反向外键一起使用:
django.core.exceptions.FieldError:在 select_related:“product_set”。选项为:(无)
实际上,如果我使用select_related()不带参数,就不会出现错误,如下所示:
# ↓ No argument
for obj in Category.objects.select_related().all():
print(obj.product_set.all())
然后,在控制台输出以下内容:
<QuerySet [<Product: Fruits Apple 10.00>, <Product: Fruits Orange 20.00>]>
<QuerySet [<Product: Vegetable Carrot 30.00>]>
<QuerySet [<Product: Meat Chicken 40.00>, <Product: Meat Beef 50.00>]>
<QuerySet [<Product: Fish Salmon 60.00>]>
但是,仍然运行5个SELECT查询,而不是如下所示的1个SELECT查询:
接下来,我用prefetch_related(“product_set”)从Category模型迭代Product模型,如下所示:
for obj in Category.objects.prefetch_related("product_set").all():
print(obj.product_set.all())
然后,在控制台输出以下内容:
<QuerySet [<Product: Fruits Apple 10.00>, <Product: Fruits Orange 20.00>]>
<QuerySet [<Product: Vegetable Carrot 30.00>]>
<QuerySet [<Product: Meat Chicken 40.00>, <Product: Meat Beef 50.00>]>
<QuerySet [<Product: Fish Salmon 60.00>]>
然后,运行2个SELECT查询,如下所示:
< many - to - many关系>
例如,Category和Product模型具有多对多的关系,如下所示:
class Category(models.Model):
name = models.CharField(max_length=20)
def __str__(self):
return self.name
class Product(models.Model):
categories = models.ManyToManyField(Category)
name = models.CharField(max_length=50)
price = models.DecimalField(decimal_places=2, max_digits=5)
def __str__(self):
return self.name + " " + str(self.price)
那么Category模型有5个对象,Product模型有6个对象,如下图所示:
<前向外键>
然后,我从Product模型迭代Category模型,如下图所示:
for obj in Product.objects.all():
print(obj.categories.all())
然后,在控制台输出以下内容:
<QuerySet [<Category: Fruits>, <Category: 20% OFF>]>
<QuerySet [<Category: Fruits>]>
<QuerySet [<Category: Vegetable>]>
<QuerySet [<Category: Meat>, <Category: 20% OFF>]>
<QuerySet [<Category: Meat>]>
<QuerySet [<Category: Fish>, <Category: 20% OFF>]>
然后,运行7个SELECT查询,如下所示:
接下来,我用select_related(“categories”)从Product模型迭代Category模型,如下所示:
for obj in Product.objects.select_related("categories").all():
print(obj.categories.all())
然后,出现以下错误,因为select_related("categories")不能与反向外键一起使用:
django.core.exceptions.FieldError:在 select_related:“类别”。选项为:(无)
实际上,如果我使用select_related()不带参数,就不会出现错误,如下所示:
# ↓ No argument
for obj in Product.objects.select_related().all():
print(obj.categories.all())
然后,在控制台输出以下内容:
<QuerySet [<Category: Fruits>, <Category: 20% OFF>]>
<QuerySet [<Category: Fruits>]>
<QuerySet [<Category: Vegetable>]>
<QuerySet [<Category: Meat>, <Category: 20% OFF>]>
<QuerySet [<Category: Meat>]>
<QuerySet [<Category: Fish>, <Category: 20% OFF>]>
但是,仍然运行7个SELECT查询,而不是如下所示的1个SELECT查询:
接下来,我迭代类别模型从产品模型prefetch_related(“类别”)如下所示:
for obj in Product.objects.prefetch_related("categories").all():
print(obj.categories.all())
然后,在控制台输出以下内容:
<QuerySet [<Category: Fruits>, <Category: 20% OFF>]>
<QuerySet [<Category: Fruits>]>
<QuerySet [<Category: Vegetable>]>
<QuerySet [<Category: Meat>, <Category: 20% OFF>]>
<QuerySet [<Category: Meat>]>
<QuerySet [<Category: Fish>, <Category: 20% OFF>]>
然后,运行2个SELECT查询,如下所示:
<反向外键>
接下来,我从Category模型中迭代Product模型,如下所示:
for obj in Category.objects.all():
print(obj.product_set.all())
然后,在控制台输出以下内容:
<QuerySet [<Product: Apple 10.00>, <Product: Orange 20.00>]>
<QuerySet [<Product: Carrot 30.00>]>
<QuerySet [<Product: Chicken 40.00>, <Product: Beef 50.00>]>
<QuerySet [<Product: Salmon 60.00>]>
<QuerySet [<Product: Apple 10.00>, <Product: Chicken 40.00>, <Product: Salmon 60.00>]>
然后,运行6个SELECT查询,如下所示:
接下来,我用select_related(“product_set”)从Category模型迭代Product模型,如下所示:
for obj in Category.objects.select_related("product_set").all():
print(obj.product_set.all())
然后,出现以下错误,因为select_related("categories")不能与反向外键一起使用:
django.core.exceptions.FieldError:在 select_related:“product_set”。选项为:(无)
实际上,如果我使用select_related()不带参数,就不会出现错误,如下所示:
# ↓ No argument
for obj in Category.objects.select_related().all():
print(obj.product_set.all())
然后,在控制台输出以下内容:
<QuerySet [<Product: Apple 10.00>, <Product: Orange 20.00>]>
<QuerySet [<Product: Carrot 30.00>]>
<QuerySet [<Product: Chicken 40.00>, <Product: Beef 50.00>]>
<QuerySet [<Product: Salmon 60.00>]>
<QuerySet [<Product: Apple 10.00>, <Product: Chicken 40.00>, <Product: Salmon 60.00>]>
但是,仍然运行7个SELECT查询,而不是如下所示的1个SELECT查询:
接下来,我用prefetch_related(“product_set”)从Category模型迭代Product模型,如下所示:
for obj in Category.objects.prefetch_related("product_set").all():
print(obj.product_set.all())
然后,在控制台输出以下内容:
<QuerySet [<Product: Apple 10.00>, <Product: Orange 20.00>]>
<QuerySet [<Product: Carrot 30.00>]>
<QuerySet [<Product: Chicken 40.00>, <Product: Beef 50.00>]>
<QuerySet [<Product: Salmon 60.00>]>
<QuerySet [<Product: Apple 10.00>, <Product: Chicken 40.00>, <Product: Salmon 60.00>]>
然后,运行2个SELECT查询,如下所示:
< >额外的实验
例如,有Country、State和City模型,它们由外键链接,如下所示:
class Country(models.Model):
name = models.CharField(max_length=20)
def __str__(self):
return self.name
class State(models.Model):
country = models.ForeignKey(Country, on_delete=models.CASCADE)
name = models.CharField(max_length=20)
def __str__(self):
return self.name
class City(models.Model):
state = models.ForeignKey(State, on_delete=models.CASCADE)
name = models.CharField(max_length=20)
def __str__(self):
return self.name
那么,Country模型有2个对象,State模型有3个对象,City有7个对象,如下图所示:
<前向外键>
然后,我用select_related("state__country")从City模型迭代Country模型,如下所示:
for obj in City.objects.all().select_related("state__country"):
print(obj.state.country)
然后,在控制台输出以下内容:
USA
USA
USA
USA
USA
Japan
Japan
然后,运行1个SELECT查询,如下所示:
接下来,我用prefetch_related("state__country")从City模型迭代Country模型,如下所示:
for obj in City.objects.all().prefetch_related("state__country"):
print(obj.state.country)
然后,在控制台输出以下内容:
USA
USA
USA
USA
USA
Japan
Japan
然后,运行3个SELECT查询,如下所示:
<反向外键>
接下来,我用prefetch_related("state_set__city_set")从国家模型迭代城市模型,如下所示:
for country_obj in Country.objects.all().prefetch_related("state_set__city_set"):
for state_obj in country_obj.state_set.all():
for city_obj in state_obj.city_set.all():
print(city_obj)
然后,在控制台输出以下内容:
San Francisco
Los Angeles
San Diego
Kansas City
St. Louis
Ginza
Akihabara
然后,运行3个SELECT查询,如下所示:
接下来,我用prefetch_related("state_set__city_set")用filter()而不是all()从国家模型迭代城市模型,如下所示。*prefetch_related() with filter()不起作用:
# Here
for country_obj in Country.objects.filter().prefetch_related("state_set__city_set"):
for state_obj in country_obj.state_set.filter(): # Here
for city_obj in state_obj.city_set.filter(): # Here
print(city_obj)
然后,在控制台输出以下内容:
San Francisco
Los Angeles
San Diego
Kansas City
St. Louis
Ginza
Akihabara
然后,运行8个SELECT查询,如下所示:
因此,为了减少8个SELECT查询,我需要使用Prefetch()和filter(),但我只知道如何使用Prefetch()和filter()从国家模型迭代国家模型,如下所示。*我在Stack Overflow上问了一个关于如何用Prefetch()和filter()从国家模型迭代城市模型的问题:
for country_obj in Country.objects.filter().prefetch_related(
Prefetch('state_set', # Here
queryset=State.objects.filter(),
to_attr='state_obj'
)
):
print(country_obj.state_obj)
然后,在控制台输出以下内容:
[<State: California>, <State: Missouri>]
[<State: Tokyo>]
然后,运行2个SELECT查询,如下所示:
让我试着向你展示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将它们合并
这两种方法都达到了相同的目的,即放弃不必要的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);
每个表都被分割成一个单独的查询,而不是执行一个可能有太多行的单一连接。