本文介绍 django.db.models.query.QuerySet.prefetch_related
的用法。
声明
prefetch_related(*lookups)
返回一个QuerySet
,它将在单个批次中自动检索每个指定查找的相关对象。
这与 select_related
的目的相似,因为两者都旨在阻止由访问相关对象引起的大量数据库查询,但策略完全不同。
select_related
通过创建 SQL 连接并在 SELECT
语句中包含相关对象的字段来工作。为此,select_related
在同一个数据库查询中获取相关对象。但是,为了避免通过 ‘many’ 关系加入会导致更大的结果集,select_related
仅限于 single-valued 关系 - 外键和一对一的关系。
另一方面,prefetch_related
对每个关系进行单独的查找,并在 Python 中执行 ‘joining’。除了 select_related
支持的外键和一对一关系之外,这允许它预取多对多和多对一对象,这是使用 select_related
无法完成的。它还支持
和 GenericRelation
的预取,但是,它必须限于一组同质的结果。例如,仅当查询限制为一个 GenericForeignKey
ContentType
时,才支持预取由 GenericForeignKey
引用的对象。
例如,假设您有以下模型:
from django.db import models
class Topping(models.Model):
name = models.CharField(max_length=30)
class Pizza(models.Model):
name = models.CharField(max_length=50)
toppings = models.ManyToManyField(Topping)
def __str__(self):
return "%s (%s)" % (
self.name,
", ".join(topping.name for topping in self.toppings.all()),
)
并运行:
>>> Pizza.objects.all()
["Hawaiian (ham, pineapple)", "Seafood (prawns, smoked salmon)"...
这样做的问题是,每次 Pizza.__str__()
请求 self.toppings.all()
时,它都必须查询数据库,因此 Pizza.objects.all()
将对 Pizza QuerySet
中的每个项目在 Toppings 表上运行查询。
我们可以使用 prefetch_related
减少到两个查询:
>>> Pizza.objects.all().prefetch_related('toppings')
这意味着每个 Pizza
都有一个 self.toppings.all()
;现在每次调用self.toppings.all()
时,不必去数据库中查找项目,它会在单个查询中填充的预取QuerySet
缓存中找到它们。
也就是说,所有相关的浇头都将在单个查询中获取,并用于制作具有相关结果的预填充缓存的QuerySets
;然后在self.toppings.all()
调用中使用这些QuerySets
。
prefetch_related()
中的附加查询在开始评估 QuerySet
并且已执行主查询之后执行。
如果您有可迭代的模型实例,则可以使用
函数在这些实例上预取相关属性。prefetch_related_objects()
请注意,主QuerySet
的结果缓存和所有指定的相关对象随后将完全加载到内存中。这改变了 QuerySets
的典型行为,它通常会尽量避免在需要之前将所有对象加载到内存中,即使在数据库中执行了查询之后也是如此。
注意
请记住,与 QuerySets
一样,任何暗示不同数据库查询的后续链接方法都将忽略先前缓存的结果,并使用新的数据库查询检索数据。因此,如果您编写以下内容:
>>> pizzas = Pizza.objects.prefetch_related('toppings')
>>> [list(pizza.toppings.filter(spicy=True)) for pizza in pizzas]
…那么 pizza.toppings.all()
已被预取的事实将无济于事。 prefetch_related('toppings')
隐含 pizza.toppings.all()
,但 pizza.toppings.filter()
是一个新的不同查询。预取的缓存在这里无能为力;事实上,它会损害性能,因为您已经完成了一个未使用的数据库查询。所以请谨慎使用此函数!
此外,如果您在
上调用 database-altering 方法 related managers
、 add()
、 remove()
或 clear()
,则该关系的任何预取缓存都将被清除。set()
您也可以使用普通的连接语法来做相关字段的相关字段。假设我们对上面的示例有一个附加模型:
class Restaurant(models.Model):
pizzas = models.ManyToManyField(Pizza, related_name='restaurants')
best_pizza = models.ForeignKey(Pizza, related_name='championed_by', on_delete=models.CASCADE)
以下都是合法的:
>>> Restaurant.objects.prefetch_related('pizzas__toppings')
这将预取属于餐馆的所有比萨饼,以及属于这些比萨饼的所有配料。这将导致总共 3 个数据库查询 - 一个用于餐厅,一个用于比萨饼,一个用于浇头。
>>> Restaurant.objects.prefetch_related('best_pizza__toppings')
这将为每家餐厅获取最好的比萨饼和最好的比萨饼的所有配料。这将在 3 个数据库查询中完成 - 一个用于餐厅,一个用于“最好的比萨”,一个用于浇头。
best_pizza
关系也可以使用 select_related
来获取,以将查询计数减少到 2:
>>> Restaurant.objects.select_related('best_pizza').prefetch_related('best_pizza__toppings')
由于预取是在主查询(包括 select_related
所需的连接)之后执行的,因此它能够检测到 best_pizza
对象已经被提取,它将再次跳过提取它们。
链接 prefetch_related
调用将累积预取的查找。要清除任何 prefetch_related
行为,请将 None
作为参数传递:
>>> non_prefetched = qs.prefetch_related(None)
使用prefetch_related
时要注意的一个区别是,由查询创建的对象可以在与其相关的不同对象之间共享,即单个 Python 模型实例可以出现在返回的对象树中的多个点上。这通常会发生在外键关系上。通常这种行为不会成为问题,实际上会节省内存和 CPU 时间。
虽然 prefetch_related
支持预取 GenericForeignKey
关系,但查询的数量将取决于数据。由于GenericForeignKey
可以引用多个表中的数据,因此需要对每个引用的表进行一次查询,而不是对所有项目进行一次查询。如果尚未获取相关行,则可能会对 ContentType
表进行其他查询。
prefetch_related
在大多数情况下将使用使用“IN”运算符的 SQL 查询来实现。这意味着对于较大的QuerySet
,可能会生成一个较大的“IN”子句,根据数据库的不同,它在解析或执行 SQL 查询时可能会出现性能问题。始终为您的用例配置文件!
请注意,如果您使用 iterator()
运行查询,则 prefetch_related()
调用将被忽略,因为这两个优化一起没有意义。
您可以使用
对象来进一步控制预取操作。Prefetch
Prefetch
最简单的形式相当于传统的基于字符串的查找:
>>> from django.db.models import Prefetch
>>> Restaurant.objects.prefetch_related(Prefetch('pizzas__toppings'))
您可以使用可选的 queryset
参数提供自定义查询集。这可用于更改查询集的默认排序:
>>> Restaurant.objects.prefetch_related(
... Prefetch('pizzas__toppings', queryset=Toppings.objects.order_by('name')))
或者在适用时调用
以进一步减少查询数量:select_related()
>>> Pizza.objects.prefetch_related(
... Prefetch('restaurants', queryset=Restaurant.objects.select_related('best_pizza')))
您还可以使用可选的 to_attr
参数将预取结果分配给自定义属性。结果将直接存储在列表中。
这允许使用不同的 QuerySet
多次预取相同的关系;例如:
>>> vegetarian_pizzas = Pizza.objects.filter(vegetarian=True)
>>> Restaurant.objects.prefetch_related(
... Prefetch('pizzas', to_attr='menu'),
... Prefetch('pizzas', queryset=vegetarian_pizzas, to_attr='vegetarian_menu'))
使用自定义 to_attr
创建的查找仍然可以像往常一样被其他查找遍历:
>>> vegetarian_pizzas = Pizza.objects.filter(vegetarian=True)
>>> Restaurant.objects.prefetch_related(
... Prefetch('pizzas', queryset=vegetarian_pizzas, to_attr='vegetarian_menu'),
... 'vegetarian_menu__toppings')
过滤预取结果时建议使用to_attr
,因为它比将过滤结果存储在相关管理器的缓存中更不模糊:
>>> queryset = Pizza.objects.filter(vegetarian=True)
>>>
>>> # Recommended:
>>> restaurants = Restaurant.objects.prefetch_related(
... Prefetch('pizzas', queryset=queryset, to_attr='vegetarian_pizzas'))
>>> vegetarian_pizzas = restaurants[0].vegetarian_pizzas
>>>
>>> # Not recommended:
>>> restaurants = Restaurant.objects.prefetch_related(
... Prefetch('pizzas', queryset=queryset))
>>> vegetarian_pizzas = restaurants[0].pizzas.all()
自定义预取也适用于单个相关关系,例如转发 ForeignKey
或 OneToOneField
。通常,您会希望对这些关系使用
,但在许多情况下,使用自定义select_related()
QuerySet
进行预取很有用:
-
您想使用
QuerySet
对相关模型执行进一步的预取。 -
您只想预取相关对象的子集。
-
您想使用
deferred fields
>>> queryset = Pizza.objects.only('name') >>> >>> restaurants = Restaurant.objects.prefetch_related( ... Prefetch('best_pizza', queryset=queryset))
当使用多个数据库时,Prefetch
将尊重您选择的数据库。如果内部查询没有指定数据库,它将使用外部查询选择的数据库。以下所有内容均有效:
>>> # Both inner and outer queries will use the 'replica' database
>>> Restaurant.objects.prefetch_related('pizzas__toppings').using('replica')
>>> Restaurant.objects.prefetch_related(
... Prefetch('pizzas__toppings'),
... ).using('replica')
>>>
>>> # Inner will use the 'replica' database; outer will use 'default' database
>>> Restaurant.objects.prefetch_related(
... Prefetch('pizzas__toppings', queryset=Toppings.objects.using('replica')),
... )
>>>
>>> # Inner will use 'replica' database; outer will use 'cold-storage' database
>>> Restaurant.objects.prefetch_related(
... Prefetch('pizzas__toppings', queryset=Toppings.objects.using('replica')),
... ).using('cold-storage')
注意
查找的顺序很重要。
举以下例子:
>>> prefetch_related('pizzas__toppings', 'pizzas')
即使它是无序的,这仍然有效,因为 'pizzas__toppings'
已经包含所有需要的信息,因此第二个参数 'pizzas'
实际上是多余的。
>>> prefetch_related('pizzas__toppings', Prefetch('pizzas', queryset=Pizza.objects.all()))
这将引发ValueError
,因为尝试重新定义先前看到的查找的查询集。请注意,作为'pizzas__toppings'
查找的一部分,创建了一个隐式查询集来遍历'pizzas'
。
>>> prefetch_related('pizza_list__toppings', Prefetch('pizzas', to_attr='pizza_list'))
这将触发 AttributeError
,因为在处理 'pizza_list__toppings'
时 'pizza_list'
尚不存在。
这种考虑不限于使用Prefetch
对象。一些高级技术可能要求以特定顺序执行查找以避免创建额外的查询;因此,建议始终仔细排序prefetch_related
参数。
相关用法
- Python Django QuerySet.select_related用法及代码示例
- Python Django QuerySet.union用法及代码示例
- Python Django QuerySet.latest用法及代码示例
- Python Django QuerySet.values用法及代码示例
- Python Django QuerySet.intersection用法及代码示例
- Python Django QuerySet.get用法及代码示例
- Python Django QuerySet.none用法及代码示例
- Python Django QuerySet.exclude用法及代码示例
- Python Django QuerySet.get_or_create用法及代码示例
- Python Django QuerySet.update_or_create用法及代码示例
- Python Django QuerySet.first用法及代码示例
- Python Django QuerySet.annotate用法及代码示例
- Python Django QuerySet.dates用法及代码示例
- Python Django QuerySet.values_list用法及代码示例
- Python Django QuerySet.select_for_update用法及代码示例
- Python Django QuerySet.order_by用法及代码示例
- Python Django QuerySet.bulk_update用法及代码示例
- Python Django QuerySet.in_bulk用法及代码示例
- Python Django QuerySet.defer用法及代码示例
- Python Django QuerySet.aggregate用法及代码示例
- Python Django QuerySet.reverse用法及代码示例
- Python Django QuerySet.count用法及代码示例
- Python Django QuerySet.exists用法及代码示例
- Python Django QuerySet.explain用法及代码示例
- Python Django QuerySet.create用法及代码示例
注:本文由纯净天空筛选整理自djangoproject.com大神的英文原创作品 django.db.models.query.QuerySet.prefetch_related。非经特殊声明,原始代码版权归原作者所有,本译文未经允许或授权,请勿转载或复制。