Django 中的 prefetch_related:讓查詢更高效的利器

更新日期: 2024 年 12 月 25 日

在使用 Django 開發時,你可能會遇到數據庫查詢效率的問題,尤其是與關聯對象進行多次查詢時。

為了提升查詢性能,Django 提供了一個非常強大的工具:prefetch_related

本文將帶你了解什麼是 prefetch_related,它的作用,以及如何正確使用它來解決查詢效率問題。


什麼是 N+1 查詢問題

假設我們有一個模型 Category,每個分類(Category)下關聯著多個服務(Service)。

在查詢所有分類及其服務時,Django 默認會執行以下操作:

  1. 查詢所有分類(1 次查詢)。
  2. 對於每個分類,單獨查詢其關聯的服務(N 次查詢)。

如果有 10 個分類,總共會執行 1 + 10 = 11 次查詢

這種因多次查詢導致的性能問題被稱為 N+1 查詢問題


直觀比喻:理解 prefetch_related

為了讓你更直觀地理解,我們用一個商店的例子來比喻:

  1. 逐步詢問 (Category.objects.all())
    • 你先問商店:「有哪些類別?」
    • 然後針對每個類別,逐一再去問:「這個類別有哪些商品?」
    • 如果有 10 個類別,你需要問商店 1 次獲取類別清單,接著 10 次問商品清單
  2. 一次搞定 (Category.objects.prefetch_related("services"))
    • 你問商店:「請一次告訴我所有類別,並同時給我每個類別的商品清單。」
    • 商店一次就準備好所有數據給你。

這樣處理後,無論類別有多少,查詢次數都保持最小化。

現在看它們在實際 Django 查詢中的差異。


實際例子:查詢分類與服務

假設你的資料庫中有以下內容:

  • 3 個分類:
    1. Electronics
    2. Books
    3. Clothing
  • 每個分類下有一些服務(Service)。

示例 1:未使用 prefetch_related

以下代碼示範了未使用 prefetch_related 時的查詢方式:

categories = Category.objects.all()
for category in categories:
    print(category.name)
    print(category.services.all())  # 預設關聯的名稱

執行的查詢次數如下:

  1. 查詢 1 次:獲取所有分類
SELECT * FROM category;
  1. 每個分類再查詢一次服務(假設有 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. 查詢 1 次:獲取所有分類
SELECT * FROM category;
  1. 查詢 1 次:一次性獲取所有服務,並在 Python 層分配到對應分類
SELECT * FROM service WHERE category_id IN (1, 2, 3);

總查詢次數:1 + 1 = 2 次


為什麼 prefetch_related 更高效?

  1. 減少查詢次數
    • 如果分類數量很大(例如 1000 個分類),每個分類都需要查詢它的服務時,Category.objects.all() 會執行 1001 次查詢
    • Category.objects.prefetch_related("services") 只需要 2 次查詢
  2. 預加載和緩存
    • prefetch_related 將所有數據一次性載入到內存,然後分配到每個分類中。
    • 從分類訪問服務時,是直接從緩存中獲取,而不是再次查詢數據庫。

prefetch_relatedselect_related 的區別

prefetch_relatedselect_related 都是用來優化查詢的工具,但適用場景不同:

方法適用場景工作原理查詢效率
select_related一對一或外鍵關係使用 SQL JOIN 合併查詢結果單次查詢,高效
prefetch_related多對多或反向關聯關係執行多次查詢,並在 Python 處理多次查詢,高效

例如:

  • select_related 適合查詢單對單關係,如 BookAuthor
  • prefetch_related 適合查詢多對多關係,如 CategoryService

實際應用與效益分析

假設你的資料庫中有 1000 個分類,每個分類有 10 個服務:

  1. 未使用 prefetch_related
    • 查詢次數:1 + 1000(分類數)= 1001 次。
  2. 使用 prefetch_related
    • 查詢次數:1(分類)+ 1(服務)= 2 次。

隨著數據量的增大,prefetch_related 的性能優勢越發明顯。


小結

核心要點

  • prefetch_related 的作用
    • 預加載關聯對象,減少數據庫查詢次數。
    • 解決 N+1 查詢問題。
  • 適用場景
    • 多對多關係。
    • 反向外鍵查詢。

高效查詢的建議

  1. 使用 select_related 查詢一對一或外鍵關係。
  2. 使用 prefetch_related 查詢多對多或反向關聯。

通過靈活運用 prefetch_relatedselect_related,你可以顯著提升 Django 應用的查詢效率!

Similar Posts

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *