Django 中的 prefetch_related:讓查詢更高效的利器
更新日期: 2024 年 12 月 25 日
在使用 Django 開發時,你可能會遇到數據庫查詢效率的問題,尤其是與關聯對象進行多次查詢時。
為了提升查詢性能,Django 提供了一個非常強大的工具:prefetch_related
。
本文將帶你了解什麼是 prefetch_related
,它的作用,以及如何正確使用它來解決查詢效率問題。
什麼是 N+1 查詢問題
?
假設我們有一個模型 Category
,每個分類(Category)下關聯著多個服務(Service)。
在查詢所有分類及其服務時,Django 默認會執行以下操作:
- 查詢所有分類(1 次查詢)。
- 對於每個分類,單獨查詢其關聯的服務(N 次查詢)。
如果有 10 個分類,總共會執行 1 + 10 = 11 次查詢。
這種因多次查詢導致的性能問題被稱為 N+1 查詢問題。
直觀比喻:理解 prefetch_related
為了讓你更直觀地理解,我們用一個商店的例子來比喻:
- 逐步詢問 (
Category.objects.all()
):- 你先問商店:「有哪些類別?」
- 然後針對每個類別,逐一再去問:「這個類別有哪些商品?」
- 如果有 10 個類別,你需要問商店 1 次獲取類別清單,接著 10 次問商品清單。
- 一次搞定 (
Category.objects.prefetch_related("services")
):- 你問商店:「請一次告訴我所有類別,並同時給我每個類別的商品清單。」
- 商店一次就準備好所有數據給你。
這樣處理後,無論類別有多少,查詢次數都保持最小化。
現在看它們在實際 Django 查詢中的差異。
實際例子:查詢分類與服務
假設你的資料庫中有以下內容:
- 3 個分類:
Electronics
Books
Clothing
- 每個分類下有一些服務(
Service
)。
示例 1:未使用 prefetch_related
以下代碼示範了未使用 prefetch_related
時的查詢方式:
categories = Category.objects.all()
for category in categories:
print(category.name)
print(category.services.all()) # 預設關聯的名稱
執行的查詢次數如下:
- 查詢 1 次:獲取所有分類
SELECT * FROM category;
- 每個分類再查詢一次服務(假設有 3 個分類,就查詢 3 次):
SELECT * FROM service WHERE category_id = 1;
SELECT * FROM service WHERE category_id = 2;
SELECT * FROM service WHERE category_id = 3;
總查詢次數:1 + 3 = 4 次
示例 2:使用 prefetch_related
使用 prefetch_related
後,可以顯著減少查詢次數:
categories = Category.objects.prefetch_related("services")
for category in categories:
print(category.name)
print(category.services.all()) # 從內存中獲取,無需查詢
執行的查詢次數如下:
- 查詢 1 次:獲取所有分類
SELECT * FROM category;
- 查詢 1 次:一次性獲取所有服務,並在 Python 層分配到對應分類
SELECT * FROM service WHERE category_id IN (1, 2, 3);
總查詢次數:1 + 1 = 2 次
為什麼 prefetch_related
更高效?
- 減少查詢次數:
- 如果分類數量很大(例如 1000 個分類),每個分類都需要查詢它的服務時,
Category.objects.all()
會執行 1001 次查詢。 - 而
Category.objects.prefetch_related("services")
只需要 2 次查詢。
- 如果分類數量很大(例如 1000 個分類),每個分類都需要查詢它的服務時,
- 預加載和緩存:
prefetch_related
將所有數據一次性載入到內存,然後分配到每個分類中。- 從分類訪問服務時,是直接從緩存中獲取,而不是再次查詢數據庫。
prefetch_related
與 select_related
的區別
prefetch_related
和 select_related
都是用來優化查詢的工具,但適用場景不同:
方法 | 適用場景 | 工作原理 | 查詢效率 |
---|---|---|---|
select_related | 一對一或外鍵關係 | 使用 SQL JOIN 合併查詢結果 | 單次查詢,高效 |
prefetch_related | 多對多或反向關聯關係 | 執行多次查詢,並在 Python 處理 | 多次查詢,高效 |
例如:
select_related
適合查詢單對單關係,如Book
和Author
。prefetch_related
適合查詢多對多關係,如Category
和Service
。
實際應用與效益分析
假設你的資料庫中有 1000 個分類,每個分類有 10 個服務:
- 未使用
prefetch_related
:- 查詢次數:1 + 1000(分類數)= 1001 次。
- 使用
prefetch_related
:- 查詢次數:1(分類)+ 1(服務)= 2 次。
隨著數據量的增大,prefetch_related
的性能優勢越發明顯。
小結
核心要點
prefetch_related
的作用:- 預加載關聯對象,減少數據庫查詢次數。
- 解決 N+1 查詢問題。
- 適用場景:
- 多對多關係。
- 反向外鍵查詢。
高效查詢的建議
- 使用
select_related
查詢一對一或外鍵關係。 - 使用
prefetch_related
查詢多對多或反向關聯。
通過靈活運用 prefetch_related
和 select_related
,你可以顯著提升 Django 應用的查詢效率!