Django 效能調校:QuerySet 使用 count() 與 len() 計算資料筆數的差異

最近在程式碼審查(Code Review)時收到了一則建議,要我把用 len() 取得資料筆數的寫法改成 count()。身為 ORM 和 Django 的新手,我翻了幾篇 Stack Overflow 的文章,發現大家通常都建議盡量使用 count 來確認資料筆數。那麼,我們就來看看原因是什麼吧。
假設我們現在想查一下服務中的 User 數量。
(如果你的使用者數量高達百萬、千萬,千萬別無聊做這種事,不然會被公司的 DevOps 大哥們抓去喝咖啡,請小心。)
qs = User.objects.all()
# 使用 queryset 的 count() 的方法
def user_count_by_count():
return qs.count()
# 使用 len() 確認的方法
def user_count_by_len():
return len(qs)這時候,如果是用 count(),RDBMS 會執行 SELECT COUNT(*) FROM User,然後將 count 的數值傳回 Python。
如果是用 len(),則會在 RDBMS 執行一次 SELECT * FROM User,然後用 len() 去計算傳回 Python 的結果。
大家常說「如果要計算資料筆數,就用 count()」。原因在於,如果想用 len() 來代替 count,在 fetch 的時候會花費 O(N) 的時間。此外,將這些結果載入網頁伺服器的記憶體時,會產生 O(N) 的空間消耗,而且因為複製資料需要時間,時間上又會額外產生 O(N) 的消耗。再加上執行 len() 本身也需要時間。這兩個過程雖然會依據基礎設施(Infrastructure)而有所不同,但據說使用 count 會快上大約兩倍。
不過,有時候用 len() 會比 count() 更有利。
qs = User.objects.all()
def prefer_len_to_count():
# 假設 User 資訊裡有 telephone 欄位,我們將這些資訊彙整成一個 list。
telephones = [p.telephone for p in qs]
# 用這些資訊做些什麼處理...
...
# 取得 qs 的數量(用途請自行想像)
len(qs)如果像上面這樣,是需要 fetch queryset 來使用的情況,用 len 會稍微有利一點點。原因是如果用 count(),就需要對 RDBMS 進行兩次存取(一次 fetch,一次 count())。
雖然這話可能會被 DevOps 大哥們罵,但如果不是要計算數量極其龐大(這個標準會依據基礎設施狀況有很大的差異)的資料,其實直接用 count 也是無妨的。
當然,在 template 中也建議使用 count。
下面的 count 寫法
{{ some_queryset.count }}會比下面使用 len 的寫法更受推薦。
{{ len(some_queryset) }}參考資料:stackoverflow.com/questions/14327036/count-vs-len-on-a-django-queryset