Neo*_*ang 244 python django django-models django-orm
在Django doc中,
select_related()"跟随"外键关系,在执行查询时选择其他相关对象数据.
prefetch_related()对每个关系进行单独查找,并在Python中进行"加入".
"在python中加入"是什么意思?有人能说明一个例子吗?
我的理解是,对于外键关系,使用select_related; 对于M2M关系,请使用prefetch_related.它是否正确?
Cra*_*sta 369
你的理解大多是正确的.select_related当您要选择的对象是单个对象时,您可以使用,OneToOneField或者a ForeignKey.prefetch_related当你要得到一套"东西"时,你会使用,所以ManyToManyField你所说的或反过来ForeignKey的.只是为了澄清我的意思"反向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
Run Code Online (Sandbox Code Playgroud)
不同之处在于select_relatedSQL连接,因此从SQL服务器获取结果作为表的一部分.prefetch_related另一方面,执行另一个查询,因此减少了原始对象中的冗余列(ModelA在上面的示例中).您可以使用prefetch_related任何可以用于的东西select_related.
权衡是prefetch_related必须创建并发送一个ID列表以选择回服务器,这可能需要一段时间.我不确定在事务中是否有一种很好的方法可以做到这一点,但我的理解是Django总是只发送一个列表并说SELECT ... WHERE pk IN(...,...,...)基本上.在这种情况下,如果预取的数据是稀疏的(假设US状态对象链接到人们的地址),这可能非常好,但如果它更接近一对一,这可能会浪费大量的通信.如果有疑问,请尝试两者,看看哪个表现更好.
上面讨论的所有内容基本上都与数据库的通信有关.然而,在Python方面,prefetch_related还有一个额外的好处,即使用单个对象来表示数据库中的每个对象.随着select_related重复的对象将Python中的每个"父"对象被创建.由于Python中的对象具有相当大的内存开销,因此这也是一个考虑因素.
小智 26
可以将一对一关系中使用正向外键和反向外键以及一对多和多对多关系中使用正向外键的SELECT多个查询减少到仅 1 个查询。SELECT
不能在一对多和多对多关系中与反向外键一起使用。
\nSELECT到 2 个查询。SELECT*你可以看我的回答解释正向外键和反向外键的含义。
\n下面显示了我在一对一、一对多和多对多关系中使用正向外键和反向外select_related()键的实验。prefetch_related()
例如,有Person和PersonDetail模型具有一对一的关系,如下所示:
# "app/models.py\n\nfrom django.db import models\n\nclass Person(models.Model):\n name = models.CharField(max_length=20)\n\n def __str__(self):\n return self.name\n\nclass PersonDetail(models.Model):\n person = models.OneToOneField(Person, on_delete=models.CASCADE)\n age = models.IntegerField()\n gender = models.CharField(max_length=20)\n\n def __str__(self):\n return str(self.age) + " " + self.gender\nRun Code Online (Sandbox Code Playgroud)\n并且,还有Person管理员PersonDetail,如下所示:
# "app/admin.py\n\nfrom django.contrib import admin\nfrom .models import Person, PersonDetail\n\nclass PersonDetailInline(admin.TabularInline):\n model = PersonDetail\n \n@admin.register(Person)\nclass PersonAdmin(admin.ModelAdmin):\n inlines = (PersonDetailInline,)\n list_display = (\'id\', \'name\')\n ordering = (\'id\',)\n\n@admin.register(PersonDetail)\nclass PersonDetailAdmin(admin.ModelAdmin):\n list_display = (\'id\', \'age\', \'gender\', \'person\')\n ordering = (\'id\',)\nRun Code Online (Sandbox Code Playgroud)\n那么,Personadmin有5个对象,如下所示:
并且,PersonDetailadmin 有 5 个对象,如下所示:
然后,我Person从PersonDetail模型迭代模型,如下所示:
for obj in PersonDetail.objects.all():\n print(obj.person)\nRun Code Online (Sandbox Code Playgroud)\n然后,在控制台上输出以下内容:
\nJohn\nDavid\nLisa\nKai\nAnna\nRun Code Online (Sandbox Code Playgroud)\n然后,SELECT运行 6 个查询,如下所示。*我使用 PostgreSQL,下面是 PostgreSQL 的查询日志,你可以看到我的答案解释如何启用和禁用 PostgreSQL 上的查询日志:
接下来,我Person从PersonDetail模型迭代模型,select_related("person")如下所示。select_related()*和的顺序all()无关紧要:
for obj in PersonDetail.objects.select_related("person").all():\n print(obj.person)\nRun Code Online (Sandbox Code Playgroud)\n然后,在控制台上输出以下内容:
\nJohn\nDavid\nLisa\nKai\nAnna\nRun Code Online (Sandbox Code Playgroud)\n然后,SELECT运行 1 个查询,如下所示:
接下来,我Person从PersonDetail模型迭代模型,prefetch_related("person")如下所示。prefetch_related()*和的顺序all()无关紧要:
for obj in PersonDetail.objects.prefetch_related("person").all():\n print(obj.person)\nRun Code Online (Sandbox Code Playgroud)\n然后,在控制台上输出以下内容:
\nJohn\nDavid\nLisa\nKai\nAnna\nRun Code Online (Sandbox Code Playgroud)\n然后,SELECT运行 2 个查询,如下所示:
接下来,我PersonDetail从Person模型迭代模型,如下所示:
for obj in Person.objects.all():\n print(obj.persondetail)\nRun Code Online (Sandbox Code Playgroud)\n然后,在控制台上输出以下内容:
\n32 Male\n18 Male\n26 Female\n36 Male\n21 Female\nRun Code Online (Sandbox Code Playgroud)\n然后,SELECT运行 6 个查询,如下所示:
接下来,我PersonDetail从Person模型迭代模型,select_related("persondetail")如下所示:
for obj in Person.objects.select_related("persondetail").all():\n print(obj.persondetail)\nRun Code Online (Sandbox Code Playgroud)\n然后,在控制台上输出以下内容:
\n32 Male\n18 Male\n26 Female\n36 Male\n21 Female\nRun Code Online (Sandbox Code Playgroud)\n然后,SELECT运行 1 个查询,如下所示:
接下来,我PersonDetail从Person模型迭代模型,prefetch_related("persondetail")如下所示:
for obj in Person.objects.prefetch_related("persondetail").all():\n print(obj.persondetail)\nRun Code Online (Sandbox Code Playgroud)\n然后,在控制台上输出以下内容:
\n32 Male\n18 Male\n26 Female\n36 Male\n21 Female\nRun Code Online (Sandbox Code Playgroud)\n然后,SELECT运行 2 个查询,如下所示:
例如,有Category和Product模型具有一对多关系,如下所示:
# "app/models.py"\n\nfrom django.db import models\n\nclass Category(models.Model):\n name = models.CharField(max_length=20)\n\n def __str__(self):\n return self.name\n\nclass Product(models.Model):\n category = models.ForeignKey(Category, on_delete=models.CASCADE)\n name = models.CharField(max_length=50)\n price = models.DecimalField(decimal_places=2, max_digits=5)\n\n def __str__(self):\n return str(self.category) + " " + self.name + " " + str(self.price)\nRun Code Online (Sandbox Code Playgroud)\n并且,还有Category管理员Product,如下所示:
# "app/admin.py"\n\nfrom django.contrib import admin\nfrom .models import Category, Product\n \n@admin.register(Category)\nclass CategoryAdmin(admin.ModelAdmin):\n list_display = (\'id\', \'name\')\n ordering = (\'id\',)\n\n@admin.register(Product)\nclass ProductAdmin(admin.ModelAdmin):\n list_display = (\'id\', \'name\', \'price\', \'category\')\n ordering = (\'id\',)\nRun Code Online (Sandbox Code Playgroud)\n那么,Categoryadmin有4个对象,如下所示:
并且,Productadmin 有 6 个对象,如下所示:
然后,我Category从Product模型迭代模型,如下所示:
for obj in Product.objects.all():\n print(obj.category)\nRun Code Online (Sandbox Code Playgroud)\n然后,在控制台上输出以下内容:
\nFruits\nFruits\nVegetable\nMeat\nMeat\nFish\nRun Code Online (Sandbox Code Playgroud)\n然后,SELECT运行 7 个查询,如下所示:
接下来,我Category从Product模型迭代模型,select_related("category")如下所示:
for obj in Product.objects.select_related("category").all():\n print(obj.category)\nRun Code Online (Sandbox Code Playgroud)\n然后,在控制台上输出以下内容:
\nFruits\nFruits\nVegetable\nMeat\nMeat\nFish\nRun Code Online (Sandbox Code Playgroud)\n然后,SELECT运行 1 个查询,如下所示:
接下来,我Category从Product模型迭代模型,prefetch_related("category")如下所示:
for obj in Product.objects.prefetch_related("category").all():\n print(obj.category)\nRun Code Online (Sandbox Code Playgroud)\n然后,在控制台上输出以下内容:
\nFruits\nFruits\nVegetable\nMeat\nMeat\nFish\nRun Code Online (Sandbox Code Playgroud)\n然后,SELECT运行 2 个查询,如下所示:
接下来,我Product从Category模型迭代模型,如下所示:
for obj in Category.objects.all():\n print(obj.product_set.all())\nRun Code Online (Sandbox Code Playgroud)\n然后,在控制台上输出以下内容:
\n<QuerySet [<Product: Fruits Apple 10.00>, <Product: Fruits Orange 20.00>]>\n<QuerySet [<Product: Vegetable Carrot 30.00>]>\n<QuerySet [<Product: Meat Chicken 40.00>, <Product: Meat Beef 50.00>]>\n<QuerySet [<Product: Fish Salmon 60.00>]>\nRun Code Online (Sandbox Code Playgroud)\n然后,SELECT运行 5 个查询,如下所示:
接下来,我尝试Product从Category模型迭代模型,select_related("product_set")如下所示:
for obj in Category.objects.select_related("product_set").all():\n print(obj.product_set.all())\nRun Code Online (Sandbox Code Playgroud)\n然后,由于无法与反向外键select_related("product_set")一起使用,因此出现以下错误:
\n\ndjango.core.exceptions.FieldError:\nselect_lated 中给出的字段名称无效:\'product_set\'。选项有:(无)
\n
select_related()实际上,如果我不带参数使用就不会出现错误,如下所示:
# \xe2\x86\x93 No argument\nfor obj in Category.objects.select_related().all():\n print(obj.product_set.all())\nRun Code Online (Sandbox Code Playgroud)\n然后,在控制台上输出以下内容:
\n<QuerySet [<Product: Fruits Apple 10.00>, <Product: Fruits Orange 20.00>]>\n<QuerySet [<Product: Vegetable Carrot 30.00>]>\n<QuerySet [<Product: Meat Chicken 40.00>, <Product: Meat Beef 50.00>]>\n<QuerySet [<Product: Fish Salmon 60.00>]>\nRun Code Online (Sandbox Code Playgroud)\n但是,SELECT仍然运行 5 个查询,而不是 1 个SELECT查询,如下所示:
接下来,我Product从Category模型迭代模型,prefetch_related("product_set")如下所示:
for obj in Category.objects.prefetch_related("product_set").all():\n print(obj.product_set.all())\nRun Code Online (Sandbox Code Playgroud)\n然后,在控制台上输出以下内容:
\n<QuerySet [<Product: Fruits Apple 10.00>, <Product: Fruits Orange 20.00>]>\n<QuerySet [<Product: Vegetable Carrot 30.00>]>\n<QuerySet [<Product: Meat Chicken 40.00>, <Product: Meat Beef 50.00>]>\n<QuerySet [<Product: Fish Salmon 60.00>]>\nRun Code Online (Sandbox Code Playgroud)\n然后,SELECT运行 2 个查询,如下所示:
例如,Category和Productmodels 之间存在多对多关系,如下所示:
# "app/models.py"\n\nfrom django.db import models\n\nclass Category(models.Model):\n name = models.CharField(max_length=20)\n\n def __str__(self):\n return self.name\n\nclass Product(models.Model):\n categories = models.ManyToManyField(Category)\n name = models.CharField(max_length=50)\n price = models.DecimalField(decimal_places=2, max_digits=5)\n \n def __str__(self):\n return self.name + " " + str(self.price)\nRun Code Online (Sandbox Code Playgroud)\n并且,还有Category管理员Product,如下所示:
# "app/admin.py\n\nfrom django.contrib import admin\nfrom .models import Category, Product\n \n@admin.register(Category)\nclass CategoryAdmin(admin.ModelAdmin):\n list_display = (\'id\', \'name\', \'get_products\')\n ordering = (\'id\',)\n\n @admin.display(description=\'products\')\n def get_products(self, obj):\n return [product.name for product in obj.product_set.all()]\n\n@admin.register(Product)\nclass ProductAdmin(admin.ModelAdmin):\n list_display = (\'id\', \'name\', \'price\', \'get_categories\')\n ordering = (\'id\',)\n\n @admin.display(description=\'categories\')\n def get_categories(self, obj):\n return [category.name for category in obj.categories.all()]\nRun Code Online (Sandbox Code Playgroud)\n那么,Categoryadmin有5个对象,如下所示:
并且,Productadmin 有 6 个对象,如下所示:
然后,我Category从Product模型迭代模型,如下所示:
for obj in Product.objects.all():\n print(obj.categories.all())\nRun Code Online (Sandbox Code Playgroud)\n然后,在控制台上输出以下内容:
\n<QuerySet [<Category: Fruits>, <Category: 20% OFF>]>\n<QuerySet [<Category: Fruits>]>\n<QuerySet [<Category: Vegetable>]>\n<QuerySet [<Category: Meat>, <Category: 20% OFF>]>\n<QuerySet [<Category: Meat>]>\n<QuerySet [<Category: Fish>, <Category: 20% OFF>]>\nRun Code Online (Sandbox Code Playgroud)\n然后,SELECT运行 7 个查询,如下所示:
接下来,我Category从Product模型迭代模型,select_related("categories")如下所示:
for obj in Product.objects.select_related("categories").all():\n print(obj.categories.all())\nRun Code Online (Sandbox Code Playgroud)\n然后,由于无法与反向外键select_related("categories")一起使用,因此出现以下错误:
\n\ndjango.core.exceptions.FieldError:\nselect_lated 中给出的字段名称无效:\'categories\'。选项有:(无)
\n
select_related()实际上,如果我不带参数使用就不会出现错误,如下所示:
# \xe2\x86\x93 No argument\nfor obj in Product.objects.select_related().all():\n print(obj.categories.all())\nRun Code Online (Sandbox Code Playgroud)\n然后,在控制台上输出以下内容:
\n<QuerySet [<Category: Fruits>, <Category: 20% OFF>]>\n<QuerySet [<Category: Fruits>]>\n<QuerySet [<Category: Vegetable>]>\n<QuerySet [<Category: Meat>, <Category: 20% OFF>]>\n<QuerySet [<Category: Meat>]>\n<QuerySet [<Category: Fish>, <Category: 20% OFF>]>\nRun Code Online (Sandbox Code Playgroud)\n但是,SELECT仍然运行 7 个查询,而不是 1 个SELECT查询,如下所示:
接下来,我Category从Product模型迭代模型,prefetch_related("categories")如下所示:
for obj in Product.objects.prefetch_related("categories").all():\n print(obj.categories.all())\nRun Code Online (Sandbox Code Playgroud)\n然后,在控制台上输出以下内容:
\n<QuerySet [<Category: Fruits>, <Category: 20% OFF>]>\n<QuerySet [<Category: Fruits>]>\n<QuerySet [<Category: Vegetable>]>\n<QuerySet [<Category: Meat>, <Category: 20% OFF>]>\n<QuerySet [<Category: Meat>]>\n<QuerySet [<Category: Fish>, <Category: 20% OFF>]>\nRun Code Online (Sandbox Code Playgroud)\n然后,SELECT运行 2 个查询,如下所示:
接下来,我Product从Category模型迭代模型,如下所示:
for obj in Category.objects.all():\n print(obj.product_set.all())\nRun Code Online (Sandbox Code Playgroud)\n然后,在控制台上输出以下内容:
\n<QuerySet [<Product: Apple 10.00>, <Product: Orange 20.00>]>\n<QuerySet [<Product: Carrot 30.00>]>\n<QuerySet [<Product: Chicken 40.00>, <Product: Beef 50.00>]>\n<QuerySet [<Product: Salmon 60.00>]>\n<QuerySet [<Product: Apple 10.00>, <Product: Chicken 40.00>, <Product: Salmon 60.00>]>\nRun Code Online (Sandbox Code Playgroud)\n然后,SELECT运行 6 个查询,如下所示:
接下来,我Product从Category模型迭代模型,select_related("product_set")如下所示:
for obj in Category.objects.select_related("product_set").all():\n print(obj.product_set.all())\nRun Code Online (Sandbox Code Playgroud)\n然后,由于无法与反向外键select_related("categories")一起使用,因此出现以下错误:
\n\ndjango.core.exceptions.FieldError:\nselect_lated 中给出的字段名称无效:\'product_set\'。选项有:(无)
\n
select_related()实际上,如果我不带参数使用就不会出现错误,如下所示:
# \xe2\x86\x93 No argument\nfor obj in Category.objects.select_related().all():\n print(obj.product_set.all())\nRun Code Online (Sandbox Code Playgroud)\n然后,在控制台上输出以下内容:
\n<QuerySet [<Product: Apple 10.00>, <Product: Orange 20.00>]>\n<QuerySet [<Product: Carrot 30.00>]>\n<QuerySet [<Product: Chicken 40.00>, <Product: Beef 50.00>]>\n<QuerySet [<Product: Salmon 60.00>]>\n<QuerySet [<Product: Apple 10.00>, <Product: Chicken 40.00>, <Product: Salmon 60.00>]>\nRun Code Online (Sandbox Code Playgroud)\n但是,SELECT仍
cdo*_*orn 20
两种方法都达到了相同的目的,放弃了不必要的数据库查询.但他们使用不同的方法来提高效率.
使用这些方法之一的唯一原因是单个大型查询比许多小型查询更可取.Django使用大型查询抢先在内存中创建模型,而不是对数据库执行按需查询.
select_related对每个查找执行连接,但扩展select以包括所有连接表的列.然而,这种方法有一个警告.
联接有可能乘以查询中的行数.当您通过外键或一对一字段执行连接时,行数不会增加.但是,多对多连接没有这种保证.因此,Django限制select_related了不会出乎意料地导致大规模加入的关系.
在"加入蟒蛇"的prefetch_related是一个小更令人震惊的话,应该是.它为要连接的每个表创建单独的查询.它使用WHERE IN子句过滤每个表,如:
SELECT "credential"."id",
"credential"."uuid",
"credential"."identity_id"
FROM "credential"
WHERE "credential"."identity_id" IN
(84706, 48746, 871441, 84713, 76492, 84621, 51472);
Run Code Online (Sandbox Code Playgroud)
而不是执行具有可能太多行的单个连接,而是将每个表拆分为单独的查询.
浏览了已经发布的答案。只是认为如果我用实际示例添加答案会更好。
假设您有 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)
Run Code Online (Sandbox Code Playgroud)
在这里您可以使用字段和使用字段的对象查询M2模型及其相关M1对象。select_relationM3prefetch_relation
然而,正如我们所提到M1的,从M2是 a的关系ForeignKey,它只返回任何对象的1 条记录M2。同样的事情也适用OneToOneField。
但是M3的关系 fromM2是 a ManyToManyField,它可能返回任意数量的M1对象。
考虑这样一种情况,您有 2 个M2对象m21,m22它们具有相同的5 个M3带有 ID 的关联对象1,2,3,4,5。当您M3为每个对象获取关联对象时M2,如果您使用 select related,这就是它的工作方式。
脚步:
m21对象。M3与m21ID 为的对象相关的对象1,2,3,4,5。m22对象和所有其他M2对象重复相同的操作。因为我们有相同1,2,3,4,5的ID两个m21,m22对象,如果我们使用select_related选项,它会查询数据库两次,这已经获取相同的ID。
相反,如果您使用 prefetch_related,当您尝试获取M2对象时,它会记录您的对象在查询M2表时返回的所有 ID(注意:仅 ID),并且作为最后一步,Django 将对M3表进行查询带有您的M2对象返回的所有 ID 的集合。并M2使用 Python 而不是数据库将它们连接到对象。
通过这种方式,您M3只查询一次所有对象,从而提高性能。
| 归档时间: |
|
| 查看次数: |
107208 次 |
| 最近记录: |