[轉] 高效使用 django 的 queryset

kingron發表於2024-06-25

1. DjangoQuerySet 是惰性的

DjangoQuerySet 對應於資料庫的若干記錄(row),透過可選的查詢來過濾。例如,下面的程式碼會得到資料庫中名字為 Dave 的所有的人:

person_set = Person.objects.filter(first_name="Dave")

上面的程式碼並沒有執行任何的資料庫查詢。你可以使用 person_set,給它加上一些過濾條件,或者將它傳給某個函式,這些操作都不會傳送給資料庫。這是對的,因為資料庫查詢是顯著影響 web 應用效能的因素之一。


2. 真正從資料庫獲得資料

要真正從資料庫獲得資料,你可以遍歷 QuerySet 或者使用 if QuerySet ,總之你用到資料時就會執行 sql。為了驗證這些,需要在 settings 里加入 LOGGING,驗證方式:

obj=models.Book.objects.filter(id=3)
# for i in obj:
#     print(i)

# if obj:
#     print("ok")

3. QuerySet 是具有 cache

當你遍歷 QuerySet 時,所有匹配的記錄會從資料庫獲取,然後轉換成 Djangomodel。這被稱為執行(evaluation)。這些 model 會儲存在 QuerySet 內建的 cache 中,這樣如果你再次遍歷這個 QuerySet,你不需要重複執行通用的查詢。

obj=models.Book.objects.filter(id=3)
# for i in obj:
#     print(i)

## models.Book.objects.filter(id=3).update(title="GO")
## obj_new=models.Book.objects.filter(id=3)

# for i in obj:
#     print(i)   #LOGGING只會列印一次

4. 驗證 cache 中是否有資料

簡單的使用 if 語句進行判斷也會完全執行整個 QuerySet 並且把資料放入 cache,雖然你並不需要這些資料!為了避免這個,可以用 exists() 方法來檢查是否有資料:

obj = Book.objects.filter(id=4)
#  exists()的檢查可以避免資料放入queryset的cache。
if obj.exists():
    print("hello world!")

5. 當 QuerySet 非常巨大時,cache 會成為問題

處理成千上萬的記錄時,將它們一次裝入記憶體是很浪費的。更糟糕的是,巨大的 QuerySet 可能會鎖住系統程序,讓你的程式瀕臨崩潰。要避免在遍歷資料的同時產生 QuerySet cache,可以使用 iterator() 方法來獲取資料,處理完資料就將其丟棄。

objs = Book.objects.all().iterator()
# iterator()可以一次只從資料庫獲取少量資料,這樣可以節省記憶體
for obj in objs:
    print(obj.name)
#BUT,再次遍歷沒有列印,因為迭代器已經在上一次遍歷(next)到最後一次了,沒得遍歷了
for obj in objs:
    print(obj.name)

當然,使用 iterator() 方法來防止生成 cache,意味著遍歷同一個 QuerySet 時會重複執行查詢。所以使用 iterator() 的時候要當心,確保你的程式碼在操作一個大的 QuerySet 時沒有重複執行查詢。


6. 總結

QuerySetcache 是用於減少程式對資料庫的查詢,在通常的使用下會保證只有在需要的時候才會查詢資料庫。使用 exists()iterator() 方法可以最佳化程式對記憶體的使用。不過,由於它們並不會生成 QuerySet cache,可能會造成額外的資料庫查詢。

相關文章