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