在Django文档中:
select_related ()“遵循”外键关系,在执行查询时选择其他相关对象数据。 prefetch_related()对每个关系执行单独的查找,并在Python中执行“连接”。
“在python中进行连接”是什么意思?谁能举例说明一下吗?
我的理解是,对于外键关系,使用select_related;对于M2M关系,使用prefetch_related。这对吗?
在Django文档中:
select_related ()“遵循”外键关系,在执行查询时选择其他相关对象数据。 prefetch_related()对每个关系执行单独的查找,并在Python中执行“连接”。
“在python中进行连接”是什么意思?谁能举例说明一下吗?
我的理解是,对于外键关系,使用select_related;对于M2M关系,使用prefetch_related。这对吗?
你的理解基本正确。当你要选择的对象是单个对象时,你要使用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);
每个表都被分割成一个单独的查询,而不是执行一个可能有太多行的单一连接。
浏览了已经公布的答案。只是觉得如果我加上一个实际的例子会更好。
假设你有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()
查询的个数只有一个。
让我试着向你展示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()可以将多个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查询,如下所示: