基於TPC-C基準的Python ORM的效能測試

zhongpeijiaoyu發表於2020-07-27

  當開發與資料庫需要在一起使用的應用程式時,物件關係對映器(ORM)通常用於Python程式設計中。Python ORM的示例是SQLAlchemy,Peewee,Pony-ORM和Django。選擇ORM效能起著至關重要的作用。但是如何比較這些工具集?ORM效能基準提供了明確的度量,但仍有很大的改進空間。我研究並擴充套件了定性的ORM基準,以幫助有需要開發需要的。定性的Python ORM基準Tortoise ORM(連結到儲存庫)分析了11種SQL查詢的六個ORM的速度。

  通常,Tortoise基準可以評估各種ORM的查詢執行速度。但是,這種測試方法存在一個缺陷:大多數ORM被選擇用於Web應用程式。在這種情況下,多個使用者經常將所有形式的查詢傳送到資料庫。因為在這種情況下沒有評估的基準測試工具能夠評估Python ORM的效能,所以我決定編寫自己的PonyORM和SQLAlchemy進行比較。作為基礎,我採用了TPC-C基準。

  自1988年以來,TPC一直在資料處理領域開發測試。它們早已成為行業標準,幾乎所有裝置供應商都在各種硬體和軟體樣本上使用它們。這些測試的主要特徵是它們專注於在儘可能接近真實條件的巨大負載下進行測試。

  TPC-C模擬倉庫網路。它包括五個同時執行的各種型別和複雜性的事務的組合。該測試的目的是評估多個虛擬使用者同時訪問資料庫時事務處理的速度。

  我決定使用適合此任務的TPC-C測試方法測試兩個Python ORM(SQLALchemy和PonyORM)。該測試的目的是評估多個虛擬使用者同時訪問資料庫時事務處理的速度。

  測試說明

  第一步是建立並填充倉庫網路的資料庫。

  該資料庫包含八個關係:

  1. 倉庫

  2. 區

  3. 訂購

  4. 訂單行

  5. 股票

  6. 專案

  7. 顧客

  8. 歷史

  Pony和SQLAlchemy的資料庫是相同的。僅索引主鍵和外來鍵。小A會自動建立這些索引。在SQLAlchemy中,我手動建立了它。

  在測試過程中,幾種虛擬使用者將不同型別的事務傳送到資料庫。每個事務包含幾個請求。總共有五種型別的交易以不同的發生概率提交處理:

  交易:

  1. 新訂單-45%

  2. 付款-43%

  3. order_status-4%

  4. 交付-4%

  5. 股票水平-4%

  發生交易的可能性與原始TPC-C測試中的相同。

  但是,請記住,由於技術上的限制以及我想測試以下處理器的效能,因此在具有64 GB以上RAM(需要大量處理器和巨大磁碟空間)的伺服器上進行了原始TPC-C測試。 ORM而不是硬體抵抗巨大負載的能力,因此此測試有所簡化。

  與TPC-C測試的主要區別如下:

  主要區別:

  1. 該測試執行的虛擬使用者少於原始測試

  2. 我的測試的表條目較少。例如:原始測試中“庫存”關係中的條目數是使用公式100,000 * W計算的,其中W是倉庫數。在此測試中為100 *W。

  3. 在TPC-C中,某些事務具有從資料庫查詢資料的多個選項。例如,在支付交易中,有一種可能性,將通過ID從資料庫中請求客戶,而另一種則是由姓和名。目前,我的測試僅按ID撥打電話。

  4. 我的測試資料庫比TPC-C少一個表。在TPC-C測試中,建立訂單後,會將其新增到Order表和NewOrder表中。訂單交付後,便從NewOrder表中將其刪除。每分鐘應用大量事務時,這可以加快工作速度;但是由於我訪問資料庫的使用者較少,所以這是不必要的。相反,在Order表中,我新增了bool屬性“ is_o_delivered”,該屬性將為False,直到交付訂單為止。

  接下來,我將簡要描述每個事務的作用。

  交易次數

  新命令

  1. 將兩個引數傳遞給事務:倉庫ID和客戶ID

  2. 使用傳遞的ID從資料庫中選擇倉庫和客戶

  3. 從資料庫中隨機選擇一個倉庫區域

  4. 生成指示訂單行數的隨機數。

  5. 建立一個Order物件

  6. 迴圈建立OrderLine物件。在迴圈的每次迭代中,從專案表中選擇一個隨機專案

  7. 更改訂單中每個專案的庫存

  付款

  1. 將兩個引數傳遞給事務:倉庫ID和客戶ID

  2. 通過傳遞的ID從資料庫中選擇倉庫和客戶

  3. 從資料庫中隨機選擇一個倉庫區域

  4. 生成一個指示付款金額的隨機數

  5. 按付款金額增加倉庫和區域的餘額

  6. 客戶餘額減少付款金額

  7. 遞增客戶付款櫃檯

  8. 客戶付款金額的總和增加

  9. 建立歷史記錄物件

  訂單狀態

  1. 傳遞客戶ID作為交易的引數

  2. 通過ID和該客戶的最後訂單選擇客戶

  3. 從訂單中獲取訂單狀態和訂單行。

  交貨

  1. 傳遞倉庫ID作為交易引數

  2. 從資料庫中選擇倉庫及其所有區域

  3. 為每個地區選擇最舊的未交付訂單。

  4. 對於每個將交貨狀態更改為True的訂單

  5. 對於每個訂單數量遞增的客戶

  庫存水平

  1. 傳遞倉庫ID作為交易引數

  2. 通過ID從資料庫中選擇倉庫

  3. 選擇該倉庫的最後20個訂單

  4. 對於訂單中的每個專案,評估專案的庫存水平

  檢測結果

  有兩個ORM參與測試:

  1. SQLAlchemy(圖形上的藍線)

  2. PonyORM(圖形上的橙色線)

  以下是通過2個並行程式訪問資料庫執行測試10分鐘的結果。使用“多重處理”模組啟動流程。

  X軸-時間(以分鐘為單位)

  Y軸-已完成的交易數

  作為DBMS,我使用PostgreSQL

  所有交易

  首先,按照TPC-C測試中的預期,我對所有五個事務進行了測試。這項測試的結果是,小A的速度大約是以前的兩倍。

  平均速度:

  · 小A-2543筆交易/分鐘

  · SQLAlchemy-1353.4事務/分鐘

  之後,我決定分別評估五筆交易中ORM的效能。以下是每筆交易的結果。

  新命令

  平均速度:

  · 小A-3349.2交易/分鐘

  · SQLAlchemy-1415.3事務/分鐘

  付款

  平均速度:

  · 小A-7175.3事務/分鐘

  · SQLAlchemy-4110.6事務/分鐘

  訂單狀態

  平均速度:

  · 小A-16645.6交易/分鐘

  · SQLAlchemy-4820.8事務/分鐘

  交貨

  平均速度:

  · SQLAlchemy-716.9事務/分鐘

  · 小A-323.5交易/分鐘

  庫存水平

  平均速度:

  · 小A-677.3交易/分鐘

  · SQLAlchemy-167.9事務/分鐘

  測試結果分析

  收到結果後,我分析了為什麼會這樣,並得出以下結論:

  在5分之4的事務中,PonyORM的速度更快,因為在生成SQL程式碼時,PonyORM會記住將Python表示式轉換為SQL的結果。因此,Pony不會在重複查詢時再次轉換該表示式,而SQLAlchemy在每次需要執行查詢時都被強制生成SQL程式碼。

  Pony中此類查詢的示例:

  stocks = select(stock for stock in Stock

  if stock.warehouse == whouse

  and stock.item in items).order_by(Stock.id).for_update()

  生成的SQL:

  SELECT “stock”.”id”, “stock”.”warehouse”, “stock”.”item”,

  “stock”.”quantity”, “stock”.”ytd”, “stock”.”order_cnt”,

  “stock”.”remote_cnt”, “stock”.”data”FROM “stock” “stock”WHERE “stock”.”warehouse” = %(p1)s

  AND “stock”.”item” IN (%(p2)s, %(p3)s)ORDER BY “stock”.”id”FOR UPDATE

  {‘p1’:7, ‘p2’:7, ‘p3’:37}

  SQLAlchemy:

  stocks = session.query(Stock).filter(

  Stock.warehouse == whouse, Stock.item.in_(

  items)).order_by(text(“id”)).with_for_update()

  生成的SQL:

  SELECT stock.id AS stock_id, stock.warehouse_id AS stock_warehouse_id,

  stock.item_id AS stock_item_id, stock.quantity AS stock_quantity,

  stock.ytd AS stock_ytd, stock.order_cnt AS stock_order_cnt,

  stock.remote_cnt AS stock_remote_cnt, stock.data AS stock_dataFROM stockWHERE stock.warehouse_id = %(warehouse_id_1)s AND stock.item_id IN

  (%(item_id_1)s, %(item_id_2)s) ORDER BY id FOR UPDATE

  {‘warehouse_id_1’: 7, ‘item_id_1’: 53, ‘item_id_2’: 54}

  但是,顯然,SQLAlchemy可以更快地執行交付型別事務,因為它可以將應用於不同物件的多個UPDATE操作組合到一個命令中。

  例:

  INFO:www.zpedu.com/sqlalchemy.engine.base.Engine:UPDATE order_line SET delivery_d=%

  (delivery_d)s WHERE order_line.id = %(order_line_id)s

  INFO:sqlalchemy.engine.base.Engine:(

  {‘delivery_d’: datetime.datetime(2020, 4, 6, 14, 33, 6, 922281),

  ‘order_line_id’: 316},

  {‘delivery_d’: datetime.datetime(2020, 4, 6, 14, 33, 6, 922272),

  ‘order_line_id’: 317},

  {‘delivery_d’: datetime.datetime(2020, 4, 6, 14, 33, 6, 922261))

  在這種情況下,小A會為每個更新傳送單獨的查詢。

  結論

  根據測試的結果,我可以說Pony從資料庫中選擇的速度更快。另一方面,在某些情況下,SQLAlchemy可以以更高的速度生成Update型別的查詢。

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章