1. 程式人生 > >Django【性能提升篇】

Django【性能提升篇】

ember varchar 博大精深 擴展 查找 時間 iterator read l數據庫

數據庫部分

一、查詢優化

二、持久化數據庫連接

  django1.6以後已經內置了數據庫持久化連接,很多人使用PostgreSQL作為它們的線上數據庫系統,而當我們連接PostgreSQL有時會顯得很慢,這裏我們可以進行優化。

沒有持久化連接,每一個網站的請求都會與數據庫建立一個連接。如果數據庫不在本地,盡管網速很快,這也將花費20-75ms.

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

技術分享圖片
DATABASES = {
    ‘default’: {
        ‘ENGINE’: ‘django.db.backends.postgresql_psycopg2’,
        ‘NAME’: ‘whoohoodb’,
        ‘CONN_MAX_AGE’: 
600, } }
View Code

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

三、select_related() 和 prefetch_related()

相比於修改數據庫存儲,這裏只要需要簡單的設置select_related()和prefetch_related(),在使用ORM的情況下,他能夠減少sql查詢的數量。
這裏有一個BlogPost模型,它有一個用戶外鍵,獲得了一個listview

queryset = BlogPost.objects.active

那麽在模版中是這樣使用:

<ul>
{% for post in object_list %}
  <li>{{ post.title }} - {{ post.user.email }}</li>
{% endfor %}
</ul>

這裏滿足了預期的效果,但是每個post都會去查詢auth_user表。為了解決這個問題,可以讓djangode的ORM在前面就連接上auth_user 表,這樣object.user就是一個可以直接用的對象了,這樣BlogPost.objects.active().count()就會變成一個簡單的查詢

修改如下:

queryset = BlogPost.objects.select_related().active()

prefetch_related的機理是相同的


當感到疑惑時,開啟django調試工具,然後分析每次請求的查詢次數和時間,如果每次查詢都要用上5~10次,那麽這些就是可以優化的線索

四、利用標準數據庫優化技術

傳統數據庫優化技術博大精深,不同的數據庫有不同的優化技巧,但重心還是有規則的。在這裏算是題外話,挑兩點通用的說說:
  索引,給關鍵的字段添加索引,性能能更上一層樓,如給表的關聯字段,搜索頻率高的字段加上索引等。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處理  

  • 使用 filter and exclude 過濾不需要的記錄,這兩個是最常用語句,相當是SQL的where
  • 同一實體裏使用F()表達式過濾其他字段
  • 使用annotate對數據庫做聚合運算

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

  • 使用QuerySet.extra() extra雖然擴展性不太好,但功能很強大,如果實體裏需要需要增加額外屬性,不得已時,通過extra來實現,也是個好辦法
  • 使用原生的SQL語句 如果發現Django的ORM已經實現不了你的需求,而extra也無濟於事的時候,那就用原生SQL語句

七、如果需要就一次性取出你所需要的數據  

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

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

八、懂減少數據庫的連接數

  使用 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)

  

六、讀寫分離

1.增加主從數據庫

技術分享圖片
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,
          }
  }
View Code

2.編輯路由分發的類 :db_router.py 技術分享圖片

技術分享圖片

技術分享圖片
class MasterSlaveDBRouter(object):
      """讀寫分離路由"""

      def db_for_read(self, model, **hints):
          """"""
          return slave

      def db_for_write(self, model, **hints):
          """"""
          return default

      def allow_relation(self, obj1, obj2, **hints):
          """是否允許關聯查詢"""
          return True
View Code

3.讀寫分離引導settings.py

技術分享圖片
# 配置讀寫分離
DATABASE_ROUTERS = [utils.db_router.MasterSlaveDBRouter]
View Code

三、redis集群

頁面渲染部分

一、模板加載

  默認django使用兩個標準的模版加載器

技術分享圖片
TEMPLATE_LOADERS = (
    ‘django.template.loaders.filesystem.Loader’,
    ‘django.template.loaders.app_directories.Loader’,
)
View Code

  每一個請求,這些模版加載器都會去文件系統搜索,然後解析這些模版。
  這裏可以感覺到,它是不是可以處理的更快了?
  你可以開啟緩存加載,因此django只會查找並且解析你的模版一次
配置如下:

技術分享圖片
TEMPLATE_LOADERS = (
    (‘django.template.loaders.cached.Loader’, (
        ‘django.template.loaders.filesystem.Loader’,
        ‘django.template.loaders.app_directories.Loader’,
    )),
)
settings

但是,不要在開發環境中開啟緩存加載,這樣會很煩人的,因為每次模版做了修改之後你都需要重啟服務才能看到修改後的效果

緩存部分

Django【性能提升篇】