Django性能優化


Django是一個基於Python的網站開發框架,一個很重要的特點就是Battery Included,簡單來說就是包含了常規開發中所需要的一切東西,包括但不限於完整的ORM模型、中間件、會話處理、模板語言、路由映射、管理員站點等,大大提高了開發者的開發體驗,今天要談的東西便是屬於Django ORM性能優化這塊的內容。

一,數據庫連接

django1.6以后已經內置了數據庫持久化連接,默認不使用持久化連接,每一個網站的請求都會與數據庫建立一個連接。如果數據庫不在本地,盡管網速很快,這也將花費20-75ms。

設置持久化連接,僅需要添加CONN_MAX_AGE參數到你的數據庫設置中:

DATABASES = {
    ‘default’: {
        ‘ENGINE’: ‘django.db.backends.postgresql_psycopg2’,
        ‘NAME’: ‘whoohoodb’,
        ‘CONN_MAX_AGE’: 600,
    }
}

通過這樣的設置,我們設置的持久化連接每次都將存活10分鍾 ,這有助於減少內存泄漏或導致一種片狀連接的問題。 
你也可設置更長的時間,但是我不要設置超過1個小時,因為這樣獲得的效果不會太好。你可以從django的幫助文檔中獲取詳細信息:https://docs.djangoproject.com/en/1.8/ref/databases/#persistent-connections

 

二,索引

對於經常使用的過濾字段,應考慮加上索引以提升查詢性能。當然索引的添加策略是一個很復雜的命題,但是在大多數情況下加上它是沒錯的。

 

三,select_related() 和 prefetch_related()

在實際的開發中,模型之間經常存在復雜的關聯關系。在數據量較大的情況下,默認的查詢可能面臨潛在的性能問題。今天我們就分享一下Django ORM的查詢優化。

首先需要明確一點:

Queryset是惰性求值的。

在Django中,所有的Queryset都是惰性的,意思是當創建一個查詢集的時候,並沒有跟數據庫發生任何交互。因此我們可以對查詢集進行級聯的filter等操作,只有在訪問Queryset的內容的時候,Django才會真正進行數據庫的訪問。而多頻率、復雜的數據庫查詢往往是性能問題最大的根源。

為了方便說明,我們定義以下model:

1 class A(models.Model):
2     foo = models.IntegerFiled()
3 
4 class B(models.Model):
5     a = models.ForeignKey(A, related_name='bs')

 

關聯關系中,外鍵的查詢依然是惰性的。當我們通過外鍵獲取一個關聯對象的時候,實際上默認獲取的是關聯對象的ID。這種情況適用於只需要ID而不需要實際的關聯對象的場景,這點在Django的文檔中有相關說明:

If you only need a foreign key value, use the foreign key value that is already on the object you’ve got, rather than getting the whole related object and taking its primary key.

不過我們實際開發中,往往需要訪問到外鍵對象的其他屬性。如果按照默認的查詢方式去遍歷取值,那么會造成多次的數據庫查詢,效率可想而知。

select_related和prefetch_related正是為了解決這個問題,他們可以達到這樣的目的:在查詢對象集合的時候,把指定的外鍵對象也一並完整查詢加載,避免后續的重復查詢。

因此,我們可以通過下面的方式來獲取B的外鍵關系對象A的信息:

1 b = B.objects.select_related('a').all()
2 
3 for temp_b in b:
4     print temp_b.a.foo

以上方式,實際上只會觸發一次數據庫查詢,會極大的提升查詢性能。

prefetch_related效果和select_related類似,不過使用的場景不同:

    1,select_related適用於外鍵和多對一的關系查詢;

    2,prefetch_related適用於一對多或者多對多的查詢。

 

四,iterator()

如果需要一次查出大批量的數據,Django默認會將數據緩存在內存中,這會造成較大的內存占用。

這里我們推薦使用iterator(),它會把結果緩存緩存在磁盤或swap中,使用的時候再一條一條取出,節省內存使用。

 

五,盡量減少數據庫查詢次數

單一動作(如:同一個頁面)需要多次連接數據庫時,最好一次性取出所有需要的數據,減少連接數據庫次數;

此類需求推薦使用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實在強得太多了。

 

六,配置主從,讀寫分離

增加主從數據庫配置:

DATABASES = {
      'default': {
          'ENGINE': 'django.db.backends.mysql',
          'NAME': 'dailyfresh',
          'HOST': '192.168.243.193', # MySQL數據庫地址(主)
          'PORT': '3306',
          'USER': 'root',
          'PASSWORD': 'mysql',
      },
      'slave': {
              'ENGINE': 'django.db.backends.mysql',
              'NAME': 'dailyfresh',
              'HOST': '192.168.243.189', # MySQL數據庫地址(從)
              'PORT': '3306',
              'USER': 'root',
              'PASSWORD': 'mysql',
          }
  }

使用的時候用using()指定對應的庫名稱即可。

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM