【odoo14】第二十一章、效能優化
阿新 • • 發佈:2021-03-16
通過odoo框架,我們可以開發大型且複雜的應用。良好的效能是實現這一目標的基礎。本章,我們將探討如何提高應用效能。同時,我們也會講解找出影響效能的因素。
本章包含以下內容:
1. 記錄集的預讀取模式
2. 將資料在記憶體中快取
3. 生成不同尺寸的圖片
4. 訪問組資料
5. 一次性建立或寫多條資料
6. 通過資料庫查詢訪問資料
7. 優化python程式碼
# 記錄集的預讀取模式
當我們訪問資料時,內部其實是執行了SQL查詢。如果我們在一個多條資料的資料集讀取資料時,由於內部執行了多條SQL語句,這可能導致系統響應會比較慢。本節,我們將探討如何通過預讀取的方式優化效率。通過如下預讀取模式,我們可以減少SQL查詢的數量,進而優化系統性能。
## 步驟
下面的程式碼是計算函式。在這個方法中,self是包含多條資料的資料集。當我們直接在資料集進行迭代查詢的時候,預取可以完美地工作:
```python
# Correct prefetching
def compute_method(self):
for rec in self:
print(rec.name)
```
但是在某些場景下,預載入將變得十分複雜。比如,當通過browse方法獲取資料的時候。在下面的例子中,我們通過for迴圈一個個的獲取資料。這時將執行多次的SQL查詢,效率就比較低了:
```python
# Incorrect prefetching
def some_action(self):
record_ids = []
self.env.cr.execute("some query to fetch record id")
for rec in self.env.cr.fetchall():
record = self.env['res.partner'].browse(rec[0])
print(record.name)
```
通過將ID的列表傳給browse方法,我們可以建立一個包含多條資料的資料集。如下程式碼,預載入將工作的非常完美:
```python
# Correct prefetching
def some_action(self):
record_ids = []
self.env.cr.execute("some query to fetch record id")
record_ids = [rec[0] for rec in self.env.cr.fetchall()]
recordset = self.env["res.partner"].browse(record_ids)
for record_id in recordset:
print(record.name)
```
這種方式,將在一個SQL查詢的情況下實現預載入。
## 原理
當我們操作多資料集的時候,通過預載入可以減少SQL查詢的數量。它可以通過一次性獲取所有的資料。通常,預載入是odoo內部自動實現的,但是在某些場景下將失去此特性。比如,我們像如下方式分割資料:
```python
recs = [r for r in recordset r.id not in [1,2,4,10]]
```
由於我們將資料集拆分成幾部分,因此odoo內部將無法實現一次性預載入。
通過正確的預載入可以大幅提高物件-關係對映(Object-Relational Mapping,ORM)的效能。當我們通過for迴圈迭代資料集的時候,在第一次迭代訪問欄位值的時候,預載入將發揮其魔力。預載入將載入資料集所有的資料。後續,我們再迭代的時候將直接通過從快取讀取。這可以將SQL查詢的複雜的由O(n)降至O(1)。
我們假設資料集有10條資料。當我們在第一次迴圈中獲取name欄位的值,他將獲取10條資料。並不只是name欄位,而是10條資料的所有欄位。後續迭代的資料將直接從快取獲取。複雜度將由10降至1。
```python
for record in recordset: # recordset with 10 records
record.name # Prefetch data of all 10 records in the first
loop
record.email # data of email will be served from the cache.
```
預載入可以獲取除\*2many欄位以外的欄位的值。即便某些欄位在迴圈體內並未使用。因為載入多餘的欄位所帶來的效能影響遠小於額外的查詢。
> 小貼士
> 有時,預載入會降低效能。這時,我們可以通過recordset.with_context(prefetch_fields=Flase)禁用預載入。
預載入機制使用的是環境記憶體儲存和檢索記錄值。這就意味著,一旦資料從資料庫檢索出來,後續所有的資料都將從快取查詢。我們可以通過env.cache獲取環境快取。我們可以使用invalidate_cache()函式禁用快取。
## 更多
如果我們分隔了資料集,ORM將重新生成帶有新的預載入上下文的資料集。這時,這些資料集將僅載入其自身代表的資料的內容。如果我們打算在預載入後加載所有的資料,我們可以使用with_pretetch()函式。在下面的例子中,我們將資料集分割為兩部分。我們在兩個記錄集中都傳遞了一個通用的預取上下文,所以當你從其中一個記錄中獲取資料時,ORM會為另一個獲取資料並將資料放入快取中以備將來使用:
```pyhon
recordset = ... # assume recordset has 10 records.
recordset1 = recordset[:5].with_prefetch(recordset._ids)
recordset2 = recordset[5:].with_prefetch(recordset._ids)
```
預取上下文不限於拆分記錄集。您也可以使用with_ prefetch()方法在多個記錄集之間擁有一個公共的預取上下文。這意味著當您從一條記錄中獲取資料時,它也會為所有其他記錄集獲取資料。
# 將資料在記憶體中快取
odoo框架提供了ormcache裝飾器管理記憶體快取。本節,我們將探討如何管理快取。
## 步驟
ORM快取類定義在/odoo/tools/cache.py中。
引入檔案:
```python
from odoo import tools
```
### ormcache
這是最常用的快取裝飾器。您需要傳遞方法輸出所依賴的引數名。下面是一個帶有ormcache裝飾器的示例方法:
```python
@tools.ormcache('mode')
def fetch_mode_data(self, mode):
# some calculations
return result
```
當我們首次呼叫該函式的時候,將會返回計算值。ormcache將會儲存mode的值及result的值。如果我們再次呼叫該函式,且mode的值為之前存在的值時,將直接返回result的值。
有時,我們的函式依賴於環境屬性。比如:
```python
@tools.ormcache('self.env.uid', 'mode')
def fetch_data(self, mode):
# some calculations
return result
```
該函式將根據當前使用者及mode的值儲存result的值。
### ormcache_context
該裝飾器與ormcache類似,不同的是它依賴於引數和上下文中的值。我們需要傳入引數名稱及上線文鍵的列表。例如,我們的輸出依賴於上下文的lang及website_id,如下:
```python
@tools.ormcache_context('mode', keys=('website_id', 'lang'))
def fetch_data(self, mode):
# some calculations
return result
```
該快取將依賴於mode及context中的值
### ormcache_multi
有些方法對多個記錄或id執行操作。如果你想在這些方法上新增快取,你可以使用ormcache_multi裝飾器。您需要傳遞multi引數,在方法呼叫期間,ORM將通過迭代該引數生成快取鍵。在這個方法中,您將需要以字典格式返回結果,並以multi引數的元素作為鍵。看看下面的例子:
```python
@tools.ormcache_multi('mode', multi='ids')
def fetch_data(self, mode, ids):
result = {}
for i in ids:
data = ... # some calculation based on ids
result[i] = data
return result
```
假設我們用[1,2,3]作為id呼叫前面的方法。該方法將返回一個結果{1:…2:…3:…}格式。ORM將基於這些鍵快取結果。如果你用[1,2,3,4,5]作為ID進行另一個呼叫,你的方法將接收[4,5]作為ID引數,所以方法將執行4和5個ID的操作,其餘的結果將從快取中提供。
## 原理
ORM快取以字典的形式儲存快取(快取查詢)。該快取的鍵將基於裝飾方法的簽名生成,結果將是值。簡單地說,當您使用x, y引數呼叫方法時,方法的結果是x+y,快取查詢將是{(x, y): x+y}。這意味著下次使用相同的引數呼叫該方法時,結果將直接從該快取中提供。這節省了計算時間,使響應更快。
ORM快取是一個記憶體快取,所以它被儲存在RAM中並佔用記憶體。不要使用ormcache來提供大型資料,例如影象或檔案。
> 警告
> 使用此裝飾器的方法永遠不應返回記錄集。如果您這樣做,它們將生成psycopg2.OperationalError,因為記錄集的基礎遊標已關閉。
你應該在純函式上使用ORM快取。純函式是指對於相同的引數總是返回相同結果的方法。這些方法的輸出僅取決於引數,因此它們返回相同的結果。如果不是這種情況,則需要在執行使快取狀態無效的操作時手動清除快取。要清除快取,呼叫clear_cache()方法:
```
self.env[model_name].clear_caches()
```
一旦清除了快取,下一個對方法的呼叫將執行該方法並將結果儲存在快取中,所有具有相同引數的後續方法呼叫都將從快取中提供服務。
## 更多
ORM快取是**Least Recently
Used**(LRU),這意味著如果一個鍵在快取中不經常使用,它將被刪除。如果你沒有正確地使用ORM快取,它可能弊大於利。例如,如果在方法中引數總是不同的,那麼每次Odoo都會先在快取中查詢,然後呼叫方法來計算。如果你想了解你的快取是如何執行的,你可以把SIGUSR1訊號傳遞給Odoo程序:
```
kill -SIGUSER1 496
```
其中,496為程序號。執行命令後,可以在日誌中看到ORM快取的狀態。
![](https://img2020.cnblogs.com/blog/1753935/202103/1753935-20210315232054520-474991656.png)
快取中的百分比是命中率。它是在快取中找到結果的成功率。如果快取的命中率太低,你應該從方法中刪除ORM快取。
# 生成不同尺寸的圖片
大圖片對任何網站來說都是麻煩的。它們增加了網頁的大小,結果使網頁變慢。這就導致了不好的SEO排名和訪問者流失。本節,我們將探索如何建立不同大小的影象;通過使用正確的影象,您可以減少網頁大小和改善頁面載入時間。
## 步驟
您將需要繼承image.mixin。如下:
```python
class LibraryBook(models.Model):
_name = 'library.book'
_description = 'Library Book'
_inherit = ['image.mixin']
```
mixin模型將自動新增5個欄位,用於儲存不同大小的圖片。
## 步驟
image.mixin例項將自動向模型新增5個新的二進位制欄位。每個欄位儲存具有不同解析度的影象。以下是這些領域及其解決方案的列表:
* image_1920: 1,920x1,920
* image_1024: 1,024x1,024
* image_512: 512x1,512
* image_256: 256x256
* image_128: 128x128
在這裡給出的所有欄位中,只有image_1920是可編輯的。其他影象欄位是隻讀的,當您更改image_1920欄位時,它們會自動更新。因此,在模型的後端表單檢視中,您需要使用image_1920欄位來允許使用者上傳影象。但這樣做,我們在表單檢視中載入大image_1920影象。但是,有一種方法可以提高效能,即在form檢視中使用image_1920影象,但是顯示較小的影象。例如,我們可以使用image_1920欄位,但顯示image_128欄位。要做到這一點,你可以使用以下語法: