{% raw %}
<!--
譯者:Github@wizardforcel
-->
# 數據庫訪問優化 #
Django的數據庫層提供了很多方法來幫助開發者充分的利用他們的數據庫。這篇文檔收集了相關文檔的一些鏈接,添加了大量提示,并且按照優化數據庫使用的步驟的概要來組織。
## 性能優先 ##
作為通用的編程實踐,性能的重要性不用多說。弄清楚你在執行什么查詢以及你的開銷花在哪里。你也可能想使用外部的項目,像django-debug-toolbar,或者直接監控數據庫的工具。
記住你可以優化速度、內存占用,甚至二者一起,這取決于你的需求。一些針對其中一個的優化會對另一個不利,但有時會對二者都有幫助。另外,數據庫進程做的工作,可能和你在Python代碼中做的相同工作不具有相同的開銷。決定你的優先級是什么,是你自己的事情,你必須要權衡利弊,按需使用它們,因為這取決于你的應用和服務器。
對于下面提到的任何事情,要記住在任何修改后驗證一下,確保修改是有利的,并且足夠有利,能超過你代碼中可讀性的下降。下面的所有建議都帶有警告,在你的環境中大體原則可能并不適用,或者會起到相反的效果。
## 使用標準數據庫優化技巧 ##
...包括:
+ 索引。在你決定哪些索引應該添加 之后,這一條具有最高優先級。使用Field.db_index或者Meta.index_together在Dhango中添加它們。考慮在你經常使用filter()、exclude()、order_by()和其它方法查詢的字段上面添加索引,因為索引有助于加速查找。注意,設計最好的索引方案是一個復雜的、數據庫相關的話題,它取決于你應用的細節。持有索引的副作用可能會超過查詢速度上的任何收益。
+ 合理使用字段類型。
我們假設你已經完成了上面這些顯而易見的事情。這篇文檔剩下的部分,著重于講解如何以不做無用功的方式使用Django。這篇文檔也沒有強調用在開銷大的操作上其它的優化技巧,像general purpose caching。
## 理解查詢集 ##
理解查詢集(QuerySets) 是通過簡單的代碼獲取較好性能至關重要的一步。特別是:
### 理解查詢集計算 ###
要避免性能問題,理解以下幾點非常重要:
+ QuerySets是延遲的。
+ 什么時候它們被計算出來。
+ 數據在內存中如何存儲。
### 理解緩存屬性 ###
和整個QuerySet的緩存相同,ORM對象的屬性的結果中也存在緩存。通常來說,不可調用的屬性會被緩存。例如下面的博客模型示例:
```
>>> entry = Entry.objects.get(id=1)
>>> entry.blog # Blog object is retrieved at this point
>>> entry.blog # cached version, no DB access
```
但是通常來講,可調用的屬性每一次都會訪問數據庫。
```
>>> entry = Entry.objects.get(id=1)
>>> entry.authors.all() # query performed
>>> entry.authors.all() # query performed again
```
要小心當你閱讀模板代碼的時候 —— 模板系統不允許使用圓括號,但是會自動調用callable對象,會隱藏上述區別。
要小心使用你自定義的屬性 —— 實現所需的緩存取決于你,例如使用cached_property裝飾符。
### 使用with模板標簽 ###
要利用QuerySet的緩存行為,你或許需要使用with模板標簽。
### 使用iterator() ###
當你有很多對象時,QuerySet的緩存行為會占用大量的內存。這種情況下,采用iterator()解決。
## 在數據庫中而不是Python中做數據庫的工作 ##
比如:
+ 在最基礎的層面上,使用過濾器和反向過濾器對數據庫進行過濾。
+ 使用F 表達式在相同模型中基于其他字段進行過濾。
+ 使用數據庫中的注解和聚合。
如果上面那些都不夠用,你可以自己生成SQL語句:
### 使用QuerySet.extra() ###
extra()是一個移植性更差,但是功能更強的方法,它允許一些SQL語句顯式添加到查詢中。如果這些還不夠強大:
### 使用原始的SQL ###
編寫你自己的自定義SQL語句,來獲取數據或者填充模型。使用django.db.connection.queries來了解Django為你編寫了什么,以及從這里開始。
## 用唯一的被或索引的列來檢索獨立對象 ##
有兩個原因在get()中,用帶有unique或者db_index的列檢索獨立對象。首先,由于查詢經過了數據庫的索引,所以會更快。其次,如果很多對象匹配查詢,查詢會更慢一些;列上的唯一性約束確保這種情況永遠不會發生。
所以,使用博客模型的例子:
```
>>> entry = Entry.objects.get(id=10)
```
會快于:
```
>>> entry = Entry.object.get(headline="News Item Title")
```
因為id被數據庫索引,而且是唯一的。
下面這樣做會十分緩慢:
```
>>> entry = Entry.objects.get(headline__startswith="News")
```
首先, headline沒有被索引,它會使查詢變得很慢:
其次,這次查找并不確保返回唯一的對象。如果查詢匹配到多于一個對象,它會在數據庫中遍歷和檢索所有這些對象。如果記錄中返回了成百上千個對象,代價是非常大的。如果數據庫運行在分布式服務器上,網絡開銷和延遲也是一大因素,代價會是它們的組合。
## 一次性檢索你需要的任何東西 ##
在不同的位置多次訪問數據庫,一次獲取一個數據集,通常來說不如在一次查詢中獲取它們更高效。如果你在一個循環中執行查詢,這尤其重要。有可能你會做很多次數據庫查詢,但只需要一次就夠了。所以:
### 使用QuerySet.select_related()和prefetch_related() ###
充分了解并使用select_related()和prefetch_related():
+ 在視圖的代碼中,
+ 以及在適當的管理器和默認管理器中。要意識到你的管理器什么時候被使用和不被使用;有時這很復雜,所以不要有任何假設。
## 不要獲取你不需要的東西 ##
### 使用QuerySet.values()和values_list() ###
當你僅僅想要一個帶有值的字典或者列表,并不需要使用ORM模型對象時,可以適當使用values()。對于在模板代碼中替換模型對象,這樣會非常有用 —— 只要字典中帶有的屬性和模板中使用的一致,就沒問題。
### 使用QuerySet.defer()和only() ###
如果一些數據庫的列你并不需要(或者大多數情況下并不需要),使用defer()和only()來避免加載它們。注意如果你確實要用到它們,ORM會在另外的查詢之中獲取它們。如果你不能夠合理地使用這些函數,不如不用。
另外,當建立起一個帶有延遲字段的模型時,要意識到一些(小的、額外的)消耗會在Django內部產生。不要不分析數據庫就盲目使用延遲字段,因為數據庫必須從磁盤中讀取大多數非text和VARCHAR數據,在結果中作為單獨的一行,即使其中的列很少。 defer()和only()方法在你可以避免加載大量文本數據,或者可能要花大量時間處理而返回給Python的字段時,特別有幫助。像往常一樣,應該先寫出個大概,之后再優化。
### 使用QuerySet.count() ###
...如果你想要獲取大小,不要使用 len(queryset)。
### 使用QuerySet.exists() ###
...如果你想要知道是否存在至少一個結果,不要使用if queryset。
但是:
### 不要過度使用 count() 和 exists() ###
如果你需要查詢集中的其他數據,就把它加載出來。
例如,假設Email模型有一個body屬性,并且和User有多對多的關聯,下面的的模板代碼是最優的:
```
{% 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 %}
```
這是因為:
+ 因為查詢集是延遲加載的,如果‘display_inbox’為False,不會查詢數據庫。
+ 使用with意味著我們為了以后的使用,把user.emails.all儲存在一個變量中,允許它的緩存被復用。
+ {% if emails %}的那一行調用了QuerySet.__bool__(),它導致user.emails.all()查詢在數據庫上執行,并且至少在第一行以一個ORM對象的形式返回。如果沒有任何結果,會返回False,反之為True。
+ {{ emails|length }}調用了QuerySet.__len__()方法,填充了緩存的剩余部分,而且并沒有執行另一次查詢。
+ for循環的迭代器訪問了已經緩存的數據。
總之,這段代碼做了零或一次查詢。唯一一個慎重的優化就是with標簽的使用。在任何位置使用QuerySet.exists()或者QuerySet.count()都會導致額外的查詢。
### 使用QuerySet.update()和delete() ###
通過QuerySet.update()使用批量的SQL UPDATE語句,而不是獲取大量對象,設置一些值再單獨保存。與此相似,在可能的地方使用批量deletes。
但是要注意,這些批量的更新方法不會在單獨的實例上面調用save()或者delete()方法,意思是任何你向這些方法添加的自定義行為都不會被執行,包括由普通數據庫對象的信號驅動的任何方法。
### 直接使用外鍵的值 ###
如果你僅僅需要外鍵當中的一個值,要使用對象上你已經取得的外鍵的值,而不是獲取整個關聯對象再得到它的主鍵。例如,執行:
```
entry.blog_id
```
而不是:
```
entry.blog.id
```
### 不要做無謂的排序 ###
排序并不是沒有代價的;每個需要排序的字段都是數據庫必須執行的操作。如果一個模型具有默認的順序(Meta.ordering),并且你并不需要它,通過在查詢集上無參調用order_by() 來移除它。
向你的數據庫添加索引可能有助于提升排序性能。
## 整體插入 ##
創建對象時,盡可能使用bulk_create()來減少SQL查詢的數量。例如:
```
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")
```
注意該方法有很多注意事項,所以確保它適用于你的情況。
這也可以用在ManyToManyFields中,所以:
```
my_band.members.add(me, my_friend)
```
...更優于:
```
my_band.members.add(me)
my_band.members.add(my_friend)
```
...其中Bands和Artists具有多對多關聯。
{% endraw %}
- 新手入門
- 從零開始
- 概覽
- 安裝
- 教程
- 第1部分:模型
- 第2部分:管理站點
- 第3部分:視圖和模板
- 第4部分:表單和通用視圖
- 第5部分:測試
- 第6部分:靜態文件
- 高級教程
- 如何編寫可重用的應用
- 為Django編寫首個補丁
- 模型層
- 模型
- 模型語法
- 元選項
- 模型類
- 查詢集
- 執行查詢
- 查找表達式
- 模型的實例
- 實例方法
- 訪問關聯對象
- 遷移
- 模式編輯器
- 編寫遷移
- 高級
- 管理器
- 原始的SQL查詢
- 聚合
- 多數據庫
- 自定義查找
- 條件表達式
- 數據庫函數
- 其它
- 遺留的數據庫
- 提供初始數據
- 優化數據庫訪問
- 視圖層
- 基礎
- URL配置
- 視圖函數
- 快捷函數
- 裝飾器
- 參考
- 內建的視圖
- TemplateResponse 對象
- 文件上傳
- 概覽
- File 對象
- 儲存API
- 管理文件
- 自定義存儲
- 基于類的視圖
- 概覽
- 內建顯示視圖
- 內建編輯視圖
- API參考
- 分類索引
- 高級
- 生成 CSV
- 生成 PDF
- 中間件
- 概覽
- 內建的中間件類
- 模板層
- 基礎
- 面向設計師
- 語言概覽
- 人性化
- 面向程序員
- 表單
- 基礎
- 概覽
- 表單API
- 內建的Widget
- 高級
- 整合媒體
- 開發過程
- 設置
- 概覽
- 應用程序
- 異常
- 概覽
- django-admin 和 manage.py
- 添加自定義的命令
- 測試
- 介紹
- 部署
- 概述
- WSGI服務器
- 部署靜態文件
- 通過email追蹤代碼錯誤
- Admin
- 管理操作
- 管理文檔生成器
- 安全
- 安全概述
- 說明Django中的安全問題
- 點擊劫持保護
- 加密簽名
- 國際化和本地化
- 概述
- 本地化WEB UI格式化輸入
- “本地特色”
- 常見的網站應用工具
- 認證
- 概覽
- 使用認證系統
- 密碼管理
- 日志
- 分頁
- 會話
- 數據驗證
- 其它核心功能
- 按需內容處理
- 重定向
- 信號
- 系統檢查框架