Django性能優化

Django數據層提供各種途徑優化數據的訪問,一個項目大量優化工作一般是放在後期來做,早期的優化是“萬惡之源”,這是前人總結的經驗,不無道理。如果事先理解Django的優化技巧,開發過程中稍稍留意,後期會省不少的工作量。

一 利用標準數據庫優化技術:

傳統數據庫優化技術博大精深,不同的數據庫有不同的優化技巧,但重心還是有規則的。在這裏算是題外話,挑兩點通用的說說:

索引,給關鍵的字段添加索引,性能能更上一層樓,如給表的關聯字段,搜索頻率高的字段加上索引等。Django建立實體的時候,支持給字段添加索引,具體參考Django.db.models.Field.db_index。按照經驗,Django建立實體之前應該早想好表的結構,儘量想到後面的擴展性,避免後面的表的結構變得面目全非。

使用適當字段類型,本來varchar就搞定的字段,就別要text類型,小細節別不關緊要,後頭數據量一上去,幾億幾億的數據,小字段很可能是大問題。


二 瞭解Django的QuerySets:

瞭解Django的QuerySets對象,對優化簡單程序有至關重要的作用。QuerySets是有緩存的,一旦取出來,它就會在內存裏呆上一段時間,儘量重用它。舉個簡單的例子:

瞭解緩存屬性:
>>> entry = Entry.objects.get(id=1)
>>> entry.blog   # 博客實體第一次取出,是要訪問數據庫的
>>> entry.blog   # 第二次再用,那它就是緩存裏的實體了,不再訪問數據庫


但下面的例子就不一樣,
>>> entry = Entry.objects.get(id=1)

>>> entry.authors.all()   # 第一次all函數會查詢數據庫

>>> entry.authors.all()   # 第二次all函數還會查詢數據庫

all,count exists是調用函數(需要連接數據庫處理結果的),注意在模板template裏的代碼,模板裏不允許括號,但如果使用此類的調用函數,一樣去連接數據庫的,能用緩存的數據就別連接到數據庫去處理結果。還要注意的是,自定義的實體屬性,如果調用函數的,記得自己加上緩存策略。

利用好模板的with標籤:
模板中多次使用的變量,要用with標籤,把它看成變量的緩存行爲吧。

使用QuerySets的iterator():
通常QuerySets先調用iterator再緩存起來,當獲取大量的實體列表而僅使用一次時,緩存行爲會耗費寶貴的內存,這時iterator()能幫到你,iterator()只調用iterator而省去了緩存步驟,顯著減少內存佔用率,具體參考相關文檔。


三 數據庫的工作就交給數據庫本身計算,別用Python處理:

1 使用 filter and exclude 過濾不需要的記錄,這兩個是最常用語句,相當是SQL的where。

2 同一實體裏使用F()表達式過濾其他字段。

3 使用annotate對數據庫做聚合運算。

不要用python語言對以上類型數據過濾篩選,同樣的結果,python處理複雜度要高,而且效率不高, 白白浪費內存。

使用QuerySet.extra():
extra雖然擴展性不太好,但功能很強大,如果實體裏需要需要增加額外屬性,不得已時,通過extra來實現,也是個好辦法。

使用原生的SQL語句:
如果發現Django的ORM已經實現不了你的需求,而extra也無濟於事的時候,那就用原生SQL語句吧,用Djangoango.db.connection.queries去實現你需要的東西。


四 如果需要就一次性取出你所需要的數據:

單一動作(如:同一個頁面)需要多次連接數據庫時,最好一次性取出所有需要的數據,減少連接數據庫次數。此類需求推薦使用QuerySet.select_related() 和 prefetch_related()。

相反,別取出你不需要的東西,模版templates裏往往只需要實體的某幾個字段而不是全部,這時QuerySet.values() 和 values_list(),對你有用,它們只取你需要的字段,返回字典dict和列表list類型的東西,在模版裏夠用即可,這可減少內存損耗,提高性能。

同樣QuerySet.defer()和only()對提高性能也有很大的幫助,一個實體裏可能有不少的字段,有些字段包含很多元數據,比如博客的正文,很多字符組成,Django獲取實體時(取出實體過程中會進行一些python類型轉換工作),我們可以延遲大量元數據字段的處理,只處理需要的關鍵字段,這時QuerySet.defer()就派上用場了,在函數裏傳入需要延時處理的字段即可;而only()和defer()是相反功能。

使用QuerySet.count()代替len(queryset),雖然這兩個處理得出的結果是一樣的,但前者性能優秀很多。同理判斷記錄存在時,QuerySet.exists()比if queryset實在強得太多了。

當然一樣的結果,在緩存裏已經存在,就別濫用count(),exists(),all()函數了。


五 懂減少數據庫的連接數:

使用 QuerySet.update() 和 delete(),這兩個函數是能批處理多條記錄的,適當使用它們事半功倍;如果可以,別一條條數據去update delete處理。

對於一次性取出來的關聯記錄,獲取外鍵的時候,直接取關聯表的屬性,而不是取關聯屬性,如:

entry.blog.id

優於

entry.blog_id


善於使用批量插入記錄,如:
Entry.objects.bulk_create([
    Entry(headline="Python 3.0 Released"),
    Entry(headline="Python 3.1 Planned")
])
優於
Entry.objects.create(headline="Python 3.0 Released")
Entry.objects.create(headline="Python 3.1 Planned")
前者只連接一次數據庫,而後者連接兩次哦。

還有相似的動作需要注意的,如:多對多的關係,
my_band.members.add(me, my_friend)
優於
my_band.members.add(me)
my_band.members.add(my_friend)

發佈了33 篇原創文章 · 獲贊 25 · 訪問量 24萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章