Django程序的優(yōu)化技巧
友情提示:
過(guò)度性能優(yōu)化是沒(méi)有必要甚至有害的,因?yàn)榛ù罅鈳?lái)的毫秒級(jí)的響應(yīng)提升你的用戶(hù)可能根本感知不到,畢竟開(kāi)發(fā)人員的時(shí)間也很寶貴。
性能優(yōu)化指標(biāo)
在對(duì)一個(gè)Web項(xiàng)目進(jìn)行性能優(yōu)化時(shí),我們通常需要評(píng)價(jià)多個(gè)指標(biāo):
- 響應(yīng)時(shí)間
- 最大并發(fā)連接數(shù)
- 代碼的行數(shù)
- 函數(shù)調(diào)用次數(shù)
- 內(nèi)存占用情況
- CPU占比
其中響應(yīng)時(shí)間(服務(wù)器從接收用戶(hù)請(qǐng)求,處理該請(qǐng)求并返回結(jié)果所需的總的時(shí)間)通常是最重要的指標(biāo),因?yàn)檫^(guò)長(zhǎng)的響應(yīng)時(shí)間會(huì)讓用戶(hù)厭倦等待,轉(zhuǎn)投其它網(wǎng)站或APP。當(dāng)你的用戶(hù)數(shù)量變得非常龐大,如何提高最大并發(fā)連接數(shù),減少內(nèi)存消耗也將變得非常重要。
在開(kāi)發(fā)環(huán)境中,我們一般建議使用django-debug-toolbar和django-silk來(lái)進(jìn)行性能監(jiān)測(cè)分析。它們提供了每次用戶(hù)請(qǐng)求的響應(yīng)時(shí)間,并告訴你程序執(zhí)行過(guò)程哪個(gè)環(huán)節(jié)(比如SQL查詢(xún))最消耗時(shí)間。
對(duì)于中大型網(wǎng)站或Web APP而言,最影響網(wǎng)站性能的就是數(shù)據(jù)庫(kù)查詢(xún)部分了。一是反復(fù)從數(shù)據(jù)庫(kù)讀寫(xiě)數(shù)據(jù)很消耗時(shí)間和計(jì)算資源,二是當(dāng)返回的查詢(xún)數(shù)據(jù)集queryset非常大時(shí)還會(huì)占據(jù)很多內(nèi)存。我們先從這部分優(yōu)化做起。
數(shù)據(jù)庫(kù)查詢(xún)優(yōu)化
利用Queryset的惰性和緩存,避免重復(fù)查詢(xún)
充分利用Django的QuerySet的惰性和自帶緩存特性,可以幫助我們減少數(shù)據(jù)庫(kù)查詢(xún)次數(shù)。比如下例中例1比例2要好。因?yàn)樵谀愦蛴∥恼聵?biāo)題后,Django不僅執(zhí)行了數(shù)據(jù)庫(kù)查詢(xún),還把查詢(xún)到的article_list放在了緩存里,下次可以在其它地方復(fù)用,而例2就不行了。
# 例1: 利用了緩存特性 - Good article_list = Article.objects.filter(title__contains="django") for article in article_list: print(article.title) # 例2: Bad for article in Article.objects.filter(title__contains="django"): print(article.title)
但有時(shí)我們只希望了解查詢(xún)的結(jié)果是否存在或查詢(xún)結(jié)果的數(shù)量,這時(shí)可以使用exists()和count()方法,如下所示。這樣就不會(huì)浪費(fèi)資源查詢(xún)一個(gè)用不到的數(shù)據(jù)集,還可以節(jié)省內(nèi)存。
# 例3: Good article_list = Article.objects.filter(title__contains="django") if article_list.exists(): print("Records found.") else: print("No records") # 例4: Good count = Article.objects.filter(title__contains="django").count()
一次查詢(xún)所有需要的關(guān)聯(lián)模型數(shù)據(jù)
假設(shè)我們有一個(gè)文章(Article)模型,其與類(lèi)別(Category)是單對(duì)多的關(guān)系(ForeignKey), 與標(biāo)簽(Tag)是多對(duì)多的關(guān)系(ManyToMany)。我們需要編寫(xiě)一個(gè)article_list的函數(shù)視圖,以列表形式顯示文章清單及每篇文章的類(lèi)別和標(biāo)簽,你的模板文件可能如下所示:
{% for article in articles %} <li>{{ article.title }} </li> <li>{{ article.category.name }}</li> <li> {% for tag in article.tags.all %} {{ tag.name }}, {% endfor %} </li> {% endfor %}
在模板里每進(jìn)行一次for循環(huán)獲取關(guān)聯(lián)對(duì)象category和tag的信息,Django就要單獨(dú)進(jìn)行一次數(shù)據(jù)庫(kù)查詢(xún),造成了極大資源浪費(fèi)。我們完全可以使用select_related方法和prefetch_related方法一次性從數(shù)據(jù)庫(kù)獲取單對(duì)多和多對(duì)多關(guān)聯(lián)模型數(shù)據(jù),這樣在模板中遍歷時(shí)Django也不會(huì)執(zhí)行數(shù)據(jù)庫(kù)查詢(xún)了。
# 僅獲取文章數(shù)據(jù) - Bad def article_list(request): articles = Article.objects.all() return render(request, 'blog/article_list.html',{'articles': articles, }) # 一次性提取關(guān)聯(lián)模型數(shù)據(jù) - Good def article_list(request): articles = Article.objects.all().select_related('category').prefecth_related('tags') return render(request, 'blog/article_list.html', {'articles': articles, })
僅查詢(xún)需要用到的數(shù)據(jù)
默認(rèn)情況下Django會(huì)從數(shù)據(jù)庫(kù)中提取所有字段,但是當(dāng)數(shù)據(jù)表有很多列很多行的時(shí)候,告訴Django提取哪些特定的字段就非常有意義了。假如我們數(shù)據(jù)庫(kù)中有100萬(wàn)篇文章,需要循環(huán)打印每篇文章的標(biāo)題。如果按例4操作,我們會(huì)將每篇文章對(duì)象的全部信息都提取出來(lái)載入到內(nèi)存中,不僅花費(fèi)更多時(shí)間查詢(xún),還會(huì)大量占用內(nèi)存,而最后只用了title這一個(gè)字段,這是完全沒(méi)有必要的。我們完全可以使用values和value_list方法按需提取數(shù)據(jù),比如只獲取文章的id和title,節(jié)省查詢(xún)時(shí)間和內(nèi)存(例6-例8)。
# 例子5: Bad article_list = Article.objects.all() if article_list: print(article.title) # 例子6: Good - 字典格式數(shù)據(jù) article_list = Article.objects.values('id', 'title') if article_list: print(article.title) # 例子7: Good - 元組格式數(shù)據(jù) article_list = Article.objects.values_list('id', 'title') if article_list: print(article.title) # 例子8: Good - 列表格式數(shù)據(jù) article_list = Article.objects.values_list('id', 'title', flat=True) if article_list: print(article.title)
除此以外,Django項(xiàng)目還可以使用defer和only這兩個(gè)查詢(xún)方法來(lái)實(shí)現(xiàn)這一點(diǎn)。第一個(gè)用于指定哪些字段不要加載,第二個(gè)用于指定只加載哪些字段。
使用分頁(yè),限制最大頁(yè)數(shù)
事實(shí)前面代碼可以進(jìn)一步優(yōu)化,比如使用分頁(yè)僅展示用戶(hù)所需要的數(shù)據(jù),而不是一下子查詢(xún)所有數(shù)據(jù)。同時(shí)使用分頁(yè)時(shí)也最好控制最大頁(yè)數(shù)。比如當(dāng)你的數(shù)據(jù)庫(kù)有100萬(wàn)篇文章時(shí),每頁(yè)即使展示100篇,也需要1萬(wàn)頁(yè)展示給你的用戶(hù),這是完全沒(méi)有必要的。你可以完全只展示前200頁(yè)的數(shù)據(jù),如下所示:
LIMIT = 100 * 200 data = Articles.objects.all()[:(LIMIT + 1)] if len(data) > LIMIT: raise ExceededLimit(LIMIT) return data
數(shù)據(jù)庫(kù)設(shè)置優(yōu)化
如果你使用單個(gè)數(shù)據(jù)庫(kù),你可以采用如下手段進(jìn)行優(yōu)化:
- 建立模型時(shí)能用CharField確定長(zhǎng)度的字段盡量不用不用TextField, 可節(jié)省存儲(chǔ)空間;
- 可以給搜索頻率高的字段屬性,在定義模型時(shí)使用索引(db_index=True);
- 持久化數(shù)據(jù)庫(kù)連接。
沒(méi)有持久化連接,Django每個(gè)請(qǐng)求都會(huì)與數(shù)據(jù)庫(kù)創(chuàng)建一個(gè)連接,直到請(qǐng)求結(jié)束,關(guān)閉連接。如果數(shù)據(jù)庫(kù)不在本地,每次建立和關(guān)閉連接也需要花費(fèi)一些時(shí)間。設(shè)置持久化連接時(shí)間,僅需要添加CONN_MAX_AGE參數(shù)到你的數(shù)據(jù)庫(kù)設(shè)置中,如下所示:
DATABASES = { ‘default': { ‘ENGINE': ‘django.db.backends.postgresql_psycopg2', ‘NAME': ‘postgres', ‘CONN_MAX_AGE': 60, # 60秒 } }
當(dāng)然CONN_MAX_AGE也不宜設(shè)置過(guò)大,因?yàn)槊總€(gè)數(shù)據(jù)庫(kù)并發(fā)連接數(shù)有上限的(比如mysql默認(rèn)的最大并發(fā)連接數(shù)是100個(gè))。如果CONN_MAX_AGE設(shè)置過(guò)大,會(huì)導(dǎo)致mysql 數(shù)據(jù)庫(kù)連接數(shù)飆升很快達(dá)到上限。當(dāng)并發(fā)請(qǐng)求數(shù)量很高時(shí),CONN_MAX_AGE應(yīng)該設(shè)低點(diǎn),比如30s, 10s或5s。當(dāng)并發(fā)請(qǐng)求數(shù)不高時(shí),這個(gè)值可以設(shè)得長(zhǎng)一點(diǎn),比如60s或5分鐘。
當(dāng)你的用戶(hù)非常多、數(shù)據(jù)量非常大時(shí),你可以考慮讀寫(xiě)分離、主從復(fù)制、分表分庫(kù)的多數(shù)據(jù)庫(kù)服務(wù)器架構(gòu)。這種架構(gòu)上的布局是對(duì)所有web開(kāi)發(fā)語(yǔ)言適用的,并不僅僅局限于Django,這里不做進(jìn)一步展開(kāi)了。
緩存
緩存是一類(lèi)可以更快的讀取數(shù)據(jù)的介質(zhì)統(tǒng)稱(chēng),也指其它可以加快數(shù)據(jù)讀取的存儲(chǔ)方式。一般用來(lái)存儲(chǔ)臨時(shí)數(shù)據(jù),常用介質(zhì)的是讀取速度很快的內(nèi)存。一般來(lái)說(shuō)從數(shù)據(jù)庫(kù)多次把所需要的數(shù)據(jù)提取出來(lái),要比從內(nèi)存或者硬盤(pán)等一次讀出來(lái)付出的成本大很多。對(duì)于中大型網(wǎng)站而言,使用緩存減少對(duì)數(shù)據(jù)庫(kù)的訪(fǎng)問(wèn)次數(shù)是提升網(wǎng)站性能的關(guān)鍵之一。
視圖緩存
from django.views.decorators.cache import cache_page @cache_page(60 * 15) def my_view(request): ...
使用@cached_property裝飾器緩存計(jì)算屬性
對(duì)于不經(jīng)常變動(dòng)的計(jì)算屬性,可以使用@cached_property裝飾器緩存結(jié)果。
緩存臨時(shí)性數(shù)據(jù)比如sessions
Django的sessions默認(rèn)是存在數(shù)據(jù)庫(kù)中的,這樣的話(huà)每一個(gè)請(qǐng)求Django都要使用sql查詢(xún)會(huì)話(huà)數(shù)據(jù),然后獲得用戶(hù)對(duì)象的信息。對(duì)于臨時(shí)性的數(shù)據(jù)比如sessions和messages,最好將它們放到緩存里,也可以減少SQL查詢(xún)次數(shù)。
SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
模版緩存
默認(rèn)情況下Django每處理一個(gè)請(qǐng)求都會(huì)使用模版加載器都會(huì)去文件系統(tǒng)搜索模板,然后渲染這些模版。你可以通過(guò)使用cached.Loader開(kāi)啟模板緩存加載。這時(shí)Django只會(huì)查找并且解析你的模版一次,可以大大提升模板渲染效率。
TEMPLATES = [{ 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [BASE_DIR / 'templates'], 'OPTIONS': { 'loaders': [ ('django.template.loaders.cached.Loader', [ 'django.template.loaders.filesystem.Loader', 'django.template.loaders.app_directories.Loader', 'path.to.custom.Loader', ]), ], }, }]
注意:不建議在開(kāi)發(fā)環(huán)境中(Debug=True)時(shí)開(kāi)啟緩存加載,因?yàn)樾薷哪0搴竽悴荒芗皶r(shí)看到修改后的效果。
另外模板文件中建議使用with標(biāo)簽緩存視圖傳來(lái)的數(shù)據(jù),便于下一次時(shí)使用。對(duì)于公用的html片段,也建議使用緩存。
{% load cache %} {% cache 500 sidebar request.user.username %} .. sidebar for logged in user .. {% endcache %}
靜態(tài)文件
壓縮 HTML、CSS 和 JavaScript等靜態(tài)文件可以節(jié)省帶寬和傳輸時(shí)間。Django 自帶的壓縮工具有GzipMiddleware 中間件和 spaceless 模板 Tag。使用Python壓縮靜態(tài)文件會(huì)影響性能,一個(gè)更好的方法是通過(guò) Apache、Nginx 等服務(wù)器來(lái)對(duì)輸出內(nèi)容進(jìn)行壓縮。例如Nginx服務(wù)器支持gzip壓縮,同時(shí)可以通過(guò)expires選項(xiàng)設(shè)置靜態(tài)文件的緩存時(shí)間。
以上就是Django程序的優(yōu)化技巧的詳細(xì)內(nèi)容,更多關(guān)于Django程序的優(yōu)化的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- GoFrame代碼優(yōu)化gconv類(lèi)型轉(zhuǎn)換避免重復(fù)定義map
- MongoDB數(shù)據(jù)庫(kù)安裝部署及警告優(yōu)化
- Django項(xiàng)目?jī)?yōu)化數(shù)據(jù)庫(kù)操作總結(jié)
- go select編譯期的優(yōu)化處理邏輯使用場(chǎng)景分析
- python3 googletrans超時(shí)報(bào)錯(cuò)問(wèn)題及翻譯工具優(yōu)化方案 附源碼
- 詳解Django中views數(shù)據(jù)查詢(xún)使用locals()函數(shù)進(jìn)行優(yōu)化
- Django serializer優(yōu)化類(lèi)視圖的實(shí)現(xiàn)示例
- 淺談優(yōu)化Django ORM中的性能問(wèn)題
- Go?內(nèi)聯(lián)優(yōu)化讓程序員愛(ài)不釋手
相關(guān)文章
Django框架之中間件MiddleWare的實(shí)現(xiàn)
這篇文章主要介紹了Django框架之中間件MiddleWare的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-12-12Python爬蟲(chóng)入門(mén)案例之回車(chē)桌面壁紙網(wǎng)美女圖片采集
讀萬(wàn)卷書(shū)不如行萬(wàn)里路,學(xué)的扎不扎實(shí)要通過(guò)實(shí)戰(zhàn)才能看出來(lái),今天小編給大家?guī)?lái)一個(gè)python爬蟲(chóng)案例,采集回車(chē)桌面網(wǎng)站的美女圖片,大家可以在過(guò)程中查缺補(bǔ)漏,看看自己掌握程度怎么樣2021-10-10Python+wxPython實(shí)現(xiàn)一個(gè)簡(jiǎn)單的音樂(lè)播放器
這篇文章主要為大家詳細(xì)介紹了如何使用Python編程語(yǔ)言和wxPython模塊創(chuàng)建一個(gè)簡(jiǎn)單的音樂(lè)播放器,文中的示例代碼講解詳細(xì),感興趣的可以了解下2023-09-09Python連接MySQL并使用fetchall()方法過(guò)濾特殊字符
這篇文章主要介紹了Python連接MySQL的方法并講解了如何使用fetchall()方法過(guò)濾特殊字符,示例環(huán)境為Ubuntu操作系統(tǒng),需要的朋友可以參考下2016-03-03深度定制Python的Flask框架開(kāi)發(fā)環(huán)境的一些技巧總結(jié)
現(xiàn)在越來(lái)越多的人使用virtualenv虛擬環(huán)境部署Python項(xiàng)目,包括針對(duì)框架的實(shí)例文件夾與版本控制布置,這里我們就來(lái)整理關(guān)于深度定制Python的Flask框架開(kāi)發(fā)環(huán)境的一些技巧總結(jié)2016-07-07Python中的NumPy實(shí)用函數(shù)整理之percentile詳解
這篇文章主要介紹了Python中的NumPy實(shí)用函數(shù)整理之percentile詳解,NumPy函數(shù)percentile()用于計(jì)算指定維度上數(shù)組元素的第?n?個(gè)百分位數(shù),返回值為標(biāo)量或者數(shù)組,需要的朋友可以參考下2023-09-09