基于TPC-C基准的Python ORM的性能测试详解

  • 来源:网络
  • 更新日期:2020-07-28

摘要:   当开发与数据库需要在一起使用的应用程序时,对象关系映射器(ORM)通常用于Python编程中。Python ORM的示例

  当开发与数据库需要在一起使用的应用程序时,对象关系映射器(ORM)通常用于Python编程中。Python ORM的示例是SQLAlchemy,Peewee,Pony-ORM和Django。选择ORM性能起着至关重要的作用。但是如何比较这些工具集?ORM性能基准提供了明确的度量,但仍有很大的改进空间。我研究并扩展了定性的ORM基准,以帮助有需要开发需要的。定性的Python ORM基准Tortoise ORM(链接到存储库)分析了11种SQL查询的六个ORM的速度。

相关学习推荐:python视频教程

  通常,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类型的查询。