本文介紹 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。非經特殊聲明,原始代碼版權歸原作者所有,本譯文未經允許或授權,請勿轉載或複製。