1. 程式人生 > >【odoo14】第二十一章、效能優化

【odoo14】第二十一章、效能優化

通過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欄位。要做到這一點,你可以使用以下語法: