把之前的一個 web 項(xiàng)目重寫了一遍,語言從原先的 PHP 換成 Python,使用了 Django 框架爆阶。項(xiàng)目因?yàn)閿?shù)據(jù)庫比較龐大萌庆,有 600M 左右的 sql 數(shù)據(jù)溶褪,一些應(yīng)用涉及到查詢和修改時,導(dǎo)致網(wǎng)頁操作十分緩慢践险。同時因?yàn)橐彩莿傞_始接觸 Django猿妈,有些代碼寫得比較蠢,后來優(yōu)化之后網(wǎng)頁操作的速度基本可以接受巍虫。故寫這篇文章記錄一下彭则。
模版自定義函數(shù)
因?yàn)閿?shù)據(jù)庫設(shè)計原因,從數(shù)據(jù)庫提取出來的某個字段的數(shù)據(jù)還需要經(jīng)過一次 base64 加密占遥,原先是將篩選出來的 QuerySet 用 for 循環(huán)去遍歷然后修改俯抖。但是,如果直接用 for 循環(huán)去遍歷 QuerySet瓦胎,Django 會把他們?nèi)窟M(jìn)行實(shí)例化芬萍,因?yàn)閿?shù)據(jù)量本身也比較大的原因,會導(dǎo)致占用大量的內(nèi)存搔啊。
因?yàn)槲蚁M诓恍薷臄?shù)據(jù)庫和盡可能少的改動代碼的前提下去解決問題柬祠,一番查閱資料之后發(fā)現(xiàn)自定義 Django 模版的函數(shù)是個不錯的辦法。
Django 模板里面有兩種方式來自定義函數(shù)负芋,分別是 simple_tag 和 filter 方式瓶盛。實(shí)現(xiàn)的方式如下。
在對應(yīng) app 下創(chuàng)建一個名為 templatetags 的目錄(不可修改目錄名),在這個目錄下創(chuàng)建一個名字任意的 py 文件惩猫,在這個 py 文件里導(dǎo)入模板類芝硬,實(shí)例化一個對象 register,然后執(zhí)行一個相應(yīng)的裝飾器即可轧房,文件代碼如下:
from django import template
from django.utils.safestring import mark_safe
register = template.Library()
@register.simple_tag
def base64en(s):
return base64.b64encode(s.encode('utf-8')).decode("utf-8")
@register.filter
def tobase64(s):
return base64.b64encode(s.encode('utf-8')).decode("utf-8")
其中拌阴,base64en(simple_tag 方式) 和 tobase64(filter 方式) 是我自定義的函數(shù)。在模版文件里調(diào)用:
{% load addontags %}
{{ sample_str|tobase64 }}
{{ base64en sample_str }}
其中奶镶,addontags 是我定義函數(shù)的 py 文件名迟赃。
兩種實(shí)現(xiàn)模式的區(qū)別為:
- simple_tag 參數(shù)無個數(shù)限制,而 filter 最多只能有2個參數(shù)
- simple_tag 不能作為 if 條件厂镇,而 filter 可以
- simple_tag 后面的參數(shù)之間的空格隨意纤壁,filter 不能有空格
我更傾向 filter 模式,比較好看捺信,嘿嘿酌媒。完成之后打開相應(yīng)頁面,速度得到明顯提升迄靠。
Model 操作優(yōu)化
只獲取需要的數(shù)據(jù)
默認(rèn)情況下秒咨,ORM 查詢的時候會把數(shù)據(jù)庫記錄對應(yīng)的所有列取出,然后轉(zhuǎn)換成 python 對象掌挚,這無疑是個很大的內(nèi)存消耗雨席。當(dāng)我們只需要某些特定列的數(shù)據(jù)時我們可以通過 values()
或 values_list()
函數(shù)來實(shí)現(xiàn),它們會將數(shù)據(jù)轉(zhuǎn)換成 dicts吠式、tuples等而不是復(fù)雜的 python 對象陡厘。
項(xiàng)目中有些 query 查詢,只是用到了部分?jǐn)?shù)據(jù)特占,我們可以通過 values() 函數(shù)簡化結(jié)果糙置。
XModel.objects.filter(xxx=yyy)[:1] # before
XModel.objects.filter(xxx=yyy)[:1].values('zzz') # after
使用 update() 代替 save()
Django 中使用 save() 更新數(shù)據(jù)時,數(shù)據(jù)庫會執(zhí)行兩次操作(select, update)摩钙,而使用 update() 時罢低,數(shù)據(jù)庫只執(zhí)行一次(update)查辩。如果只是更改數(shù)據(jù)而不需要用到查詢結(jié)果胖笛,建議直接換成 update()。
# before
x = XModel.objects.filter(xxx=yyy)
x.zzz = 'aaa'
x.save()
# after
XModel.objects.filter(xxx=yyy).update(zzz='aaa')
使用 create() 代替 save()
同樣的宜岛,Django 中使用 save() 插入數(shù)據(jù)時长踊,數(shù)據(jù)庫會執(zhí)行兩次操作(select, insert),而使用 create() 時萍倡,數(shù)據(jù)庫只執(zhí)行一次(insert)身弊。如果只是為了創(chuàng)建新數(shù)據(jù)到數(shù)據(jù)庫,建議直接換成 create()。
# before
x = XModel(zzz='aaa')
x.save()
# after
XModel.objects.create(zzz='aaa')
使用緩存
在我的 web 項(xiàng)目中阱佛,有幾個頁面其實(shí)不是數(shù)據(jù)讀取特別頻繁的頁面帖汞,這些頁面其實(shí)可以設(shè)置緩存時間,這樣在一段時間內(nèi)用戶訪問時讀取緩存就好了凑术。
因?yàn)榉?wù)器內(nèi)存比較小翩蘸,這里直接使用文件緩存,設(shè)置如下淮逊。
配置文件中:
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
'LOCATION': os.path.join(BASE_DIR, '.temp'),
}
}
接著在需要緩存的路徑方法中加入 cache_page 修飾符催首,例如:
@cache_page(86400)
def index(request):
...
其中 cache_page 的參數(shù)即為緩存時間,單位為秒泄鹏,86400 即一天郎任。
總結(jié)
因?yàn)轫?xiàng)目比較輕量,沒有涉及到很復(fù)雜的數(shù)據(jù)交互备籽,在完成上述優(yōu)化之后舶治,運(yùn)行速度已經(jīng)可以接受,個人還是比較滿意的:)
注:本作品采用知識共享署名-非商業(yè)性使用-禁止演繹 3.0 未本地化版本許可協(xié)議進(jìn)行許可胶台。