Django的數(shù)據(jù)庫層提供了很多方法來幫助開發(fā)者充分的利用他們的數(shù)據(jù)庫。這篇文檔收集了相關(guān)文檔的一些鏈接,添加了大量提示,并且按照優(yōu)化數(shù)據(jù)庫使用的步驟的概要來組織。
作為通用的編程實(shí)踐,性能的重要性不用多說。弄清楚你在執(zhí)行什么查詢以及你的開銷花在哪里。你也可能想使用外部的項(xiàng)目,像django-debug-toolbar,或者直接監(jiān)控?cái)?shù)據(jù)庫的工具。
記住你可以優(yōu)化速度、內(nèi)存占用,甚至二者一起,這取決于你的需求。一些針對(duì)其中一個(gè)的優(yōu)化會(huì)對(duì)另一個(gè)不利,但有時(shí)會(huì)對(duì)二者都有幫助。另外,數(shù)據(jù)庫進(jìn)程做的工作,可能和你在Python代碼中做的相同工作不具有相同的開銷。決定你的優(yōu)先級(jí)是什么,是你自己的事情,你必須要權(quán)衡利弊,按需使用它們,因?yàn)檫@取決于你的應(yīng)用和服務(wù)器。
對(duì)于下面提到的任何事情,要記住在任何修改后驗(yàn)證一下,確保修改是有利的,并且足夠有利,能超過你代碼中可讀性的下降。下面的所有建議都帶有警告,在你的環(huán)境中大體原則可能并不適用,或者會(huì)起到相反的效果。
...包括:
我們假設(shè)你已經(jīng)完成了上面這些顯而易見的事情。這篇文檔剩下的部分,著重于講解如何以不做無用功的方式使用Django。這篇文檔也沒有強(qiáng)調(diào)用在開銷大的操作上其它的優(yōu)化技巧,像general purpose caching。
理解查詢集(QuerySets) 是通過簡(jiǎn)單的代碼獲取較好性能至關(guān)重要的一步。特別是:
要避免性能問題,理解以下幾點(diǎn)非常重要:
和整個(gè)QuerySet的緩存相同,ORM對(duì)象的屬性的結(jié)果中也存在緩存。通常來說,不可調(diào)用的屬性會(huì)被緩存。例如下面的博客模型示例:
>>> entry = Entry.objects.get(id=1)
>>> entry.blog # Blog object is retrieved at this point
>>> entry.blog # cached version, no DB access
但是通常來講,可調(diào)用的屬性每一次都會(huì)訪問數(shù)據(jù)庫。
>>> entry = Entry.objects.get(id=1)
>>> entry.authors.all() # query performed
>>> entry.authors.all() # query performed again
要小心當(dāng)你閱讀模板代碼的時(shí)候 —— 模板系統(tǒng)不允許使用圓括號(hào),但是會(huì)自動(dòng)調(diào)用callable對(duì)象,會(huì)隱藏上述區(qū)別。
要小心使用你自定義的屬性 —— 實(shí)現(xiàn)所需的緩存取決于你,例如使用cached_property裝飾符。
要利用QuerySet的緩存行為,你或許需要使用with模板標(biāo)簽。
當(dāng)你有很多對(duì)象時(shí),QuerySet的緩存行為會(huì)占用大量的內(nèi)存。這種情況下,采用iterator()解決。
比如:
如果上面那些都不夠用,你可以自己生成SQL語句:
extra()是一個(gè)移植性更差,但是功能更強(qiáng)的方法,它允許一些SQL語句顯式添加到查詢中。如果這些還不夠強(qiáng)大:
編寫你自己的自定義SQL語句,來獲取數(shù)據(jù)或者填充模型。使用django.db.connection.queries來了解Django為你編寫了什么,以及從這里開始。
有兩個(gè)原因在get()中,用帶有unique或者db_index的列檢索獨(dú)立對(duì)象。首先,由于查詢經(jīng)過了數(shù)據(jù)庫的索引,所以會(huì)更快。其次,如果很多對(duì)象匹配查詢,查詢會(huì)更慢一些;列上的唯一性約束確保這種情況永遠(yuǎn)不會(huì)發(fā)生。
所以,使用博客模型的例子:
>>> entry = Entry.objects.get(id=10)
會(huì)快于:
>>> entry = Entry.object.get(headline="News Item Title")
因?yàn)閕d被數(shù)據(jù)庫索引,而且是唯一的。
下面這樣做會(huì)十分緩慢:
>>> entry = Entry.objects.get(headline__startswith="News")
首先, headline沒有被索引,它會(huì)使查詢變得很慢:
其次,這次查找并不確保返回唯一的對(duì)象。如果查詢匹配到多于一個(gè)對(duì)象,它會(huì)在數(shù)據(jù)庫中遍歷和檢索所有這些對(duì)象。如果記錄中返回了成百上千個(gè)對(duì)象,代價(jià)是非常大的。如果數(shù)據(jù)庫運(yùn)行在分布式服務(wù)器上,網(wǎng)絡(luò)開銷和延遲也是一大因素,代價(jià)會(huì)是它們的組合。
在不同的位置多次訪問數(shù)據(jù)庫,一次獲取一個(gè)數(shù)據(jù)集,通常來說不如在一次查詢中獲取它們更高效。如果你在一個(gè)循環(huán)中執(zhí)行查詢,這尤其重要。有可能你會(huì)做很多次數(shù)據(jù)庫查詢,但只需要一次就夠了。所以:
充分了解并使用select_related()和prefetch_related():
當(dāng)你僅僅想要一個(gè)帶有值的字典或者列表,并不需要使用ORM模型對(duì)象時(shí),可以適當(dāng)使用values()。對(duì)于在模板代碼中替換模型對(duì)象,這樣會(huì)非常有用 —— 只要字典中帶有的屬性和模板中使用的一致,就沒問題。
如果一些數(shù)據(jù)庫的列你并不需要(或者大多數(shù)情況下并不需要),使用defer()和only()來避免加載它們。注意如果你確實(shí)要用到它們,ORM會(huì)在另外的查詢之中獲取它們。如果你不能夠合理地使用這些函數(shù),不如不用。
另外,當(dāng)建立起一個(gè)帶有延遲字段的模型時(shí),要意識(shí)到一些(小的、額外的)消耗會(huì)在Django內(nèi)部產(chǎn)生。不要不分析數(shù)據(jù)庫就盲目使用延遲字段,因?yàn)閿?shù)據(jù)庫必須從磁盤中讀取大多數(shù)非text和VARCHAR數(shù)據(jù),在結(jié)果中作為單獨(dú)的一行,即使其中的列很少。 defer()和only()方法在你可以避免加載大量文本數(shù)據(jù),或者可能要花大量時(shí)間處理而返回給Python的字段時(shí),特別有幫助。像往常一樣,應(yīng)該先寫出個(gè)大概,之后再優(yōu)化。
...如果你想要獲取大小,不要使用 len(queryset)。
...如果你想要知道是否存在至少一個(gè)結(jié)果,不要使用if queryset。
但是:
如果你需要查詢集中的其他數(shù)據(jù),就把它加載出來。
例如,假設(shè)Email模型有一個(gè)body屬性,并且和User有多對(duì)多的關(guān)聯(lián),下面的的模板代碼是最優(yōu)的:
{% if display_inbox %}
{% with emails=user.emails.all %}
{% if emails %}
<p>You have {{ emails|length }} email(s)</p>
{% for email in emails %}
<p>{{ email.body }}</p>
{% endfor %}
{% else %}
<p>No messages today.</p>
{% endif %}
{% endwith %}
{% endif %}
這是因?yàn)椋?/p>
總之,這段代碼做了零或一次查詢。唯一一個(gè)慎重的優(yōu)化就是with標(biāo)簽的使用。在任何位置使用QuerySet.exists()或者QuerySet.count()都會(huì)導(dǎo)致額外的查詢。
通過QuerySet.update()使用批量的SQL UPDATE語句,而不是獲取大量對(duì)象,設(shè)置一些值再單獨(dú)保存。與此相似,在可能的地方使用批量deletes。
但是要注意,這些批量的更新方法不會(huì)在單獨(dú)的實(shí)例上面調(diào)用save()或者delete()方法,意思是任何你向這些方法添加的自定義行為都不會(huì)被執(zhí)行,包括由普通數(shù)據(jù)庫對(duì)象的信號(hào)驅(qū)動(dòng)的任何方法。
如果你僅僅需要外鍵當(dāng)中的一個(gè)值,要使用對(duì)象上你已經(jīng)取得的外鍵的值,而不是獲取整個(gè)關(guān)聯(lián)對(duì)象再得到它的主鍵。例如,執(zhí)行:
entry.blog_id
而不是:
entry.blog.id
排序并不是沒有代價(jià)的;每個(gè)需要排序的字段都是數(shù)據(jù)庫必須執(zhí)行的操作。如果一個(gè)模型具有默認(rèn)的順序(Meta.ordering),并且你并不需要它,通過在查詢集上無參調(diào)用order_by() 來移除它。
向你的數(shù)據(jù)庫添加索引可能有助于提升排序性能。
創(chuàng)建對(duì)象時(shí),盡可能使用bulk_create()來減少SQL查詢的數(shù)量。例如:
Entry.objects.bulk_create([
Entry(headline="Python 3.0 Released"),
Entry(headline="Python 3.1 Planned")
])
...更優(yōu)于:
Entry.objects.create(headline="Python 3.0 Released")
Entry.objects.create(headline="Python 3.1 Planned")
注意該方法有很多注意事項(xiàng),所以確保它適用于你的情況。
這也可以用在ManyToManyFields中,所以:
my_band.members.add(me, my_friend)
...更優(yōu)于:
my_band.members.add(me)
my_band.members.add(my_friend)
...其中Bands和Artists具有多對(duì)多關(guān)聯(lián)。
{% endraw %}