Orchid ORM 基准测试

136 阅读5分钟

接下来的基准测试将对不同的对象关系映射(ORM)工具以及查询构建器的每秒操作数进行衡量。所有相关代码和说明都可在此仓库中找到。

这些测量数据是在 2023 年 6 月获取的。由于处于活跃维护状态的库会持续更新,所以请将这些基准测试结果仅看作近似值,并且可能已经过时。

参与此次测试的对象包括:

  • Orchid ORM,版本为 v1.11.0
  • Prisma,版本为 v4.15.0
  • Sequelize,版本为 v6.32.0
  • Knex,版本为 v2.4.2
  • Kysely,版本为 v0.25.0

若要对 Drizzle ORM 进行比较,可以查看这些基准测试

每个 ORM 的连接池大小都设置为 10。基准测试会运行 10 个并行查询,持续时间为 10 秒,以此来计算平均每秒操作数(ops/s)指标。

测试结果来自于我的笔记本电脑,其配置为 Intel Core i7 10 代 U 系列处理器,16GB 内存,操作系统为 Manjaro Linux。

为了确保在不同的 ORM 之间,数据库端没有缓存数据,每个 ORM 都使用单独的数据库进行测试。

从单表加载所有记录

此测试衡量从包含 10 列和 100 条记录的表中执行简单全量查询的性能。
ops/s(每秒操作数):数值越高表示性能越好。

ops/s.png

加载嵌套关系记录

在这项测试中,我们构建了一个包含多层关联的数据模型:

  • 创建 30 个用户
  • 每个用户关联 1 个帖子(帖子属于用户)
  • 每个帖子关联 5 个标签(通过中间表实现多对多关联)
  • 每个帖子关联 10 条评论(评论同时属于帖子和用户)

测试目标是获取所有帖子及其完整关联数据:包括帖子作者、标签列表、最新 10 条评论及评论作者信息。

Orchid ORM 实现示例

await db.post
  .select('id', 'title', 'description', {
    author: (q) => q.author.select('id', 'firstName', 'lastName'),
    tags: (q) => q.postTags.pluck('tagName'),
    lastComments: (q) =>
      q.comments
        .select('id', 'text', {
          author: (q) => q.author.select('id', 'firstName', 'lastName'),
        })
        .order({ createdAt: 'DESC' })
        .limit(10),
  })
  .order({ createdAt: 'DESC' });

各 ORM 实现差异说明

  • Orchid ORM:采用单 SQL 查询方案,利用JOIN LATERAL语法高效加载所有嵌套数据
  • Prisma:通过多个独立查询加载各层关联数据,在本地测试环境中网络延迟影响较小
  • Sequelize:使用UNION ALL语句合并数据,查询效率较低
  • Kysely/Knex:由于实现复杂度较高,未参与此项测试

性能指标:ops/s(每秒操作数)值越高表示处理复杂嵌套查询的性能越好

ops/s.png

简单插入测试

此测试衡量向数据库插入一条包含 7 列数据的单条记录的性能。
ops/s(每秒操作数):数值越高表示插入性能越好。

ops/s.png

嵌套插入测试

此测试衡量向数据库插入一条包含复杂关联关系的数据的性能,具体包括:

  • 插入一个帖子(Post)
  • 同时为该帖子关联 3 条评论(Comments)
  • 同时为该帖子关联 5 个标签(Tags,通过中间表实现多对多关联)
const tagNames = ['one', 'two', 'three', 'four', 'five'];

const postData = {
  userId: 1,
  title: 'Post title',
  description: 'Post description',
};

const commentData = {
  userId: 1,
  text: 'Comment text',
};

await db.post.insert({
  ...postData,
  comments: {
    create: [commentData, commentData, commentData],
  },
  postTags: {
    create: tagNames.map((tagName) => ({ tagName })),
  },
});

各 ORM 支持情况说明

  • Orchid ORM:支持通过单条语句完成嵌套对象的级联插入
  • Prisma:唯一参与此项测试的其他 ORM,支持类似的嵌套插入语法
  • Sequelize:由于实现复杂度高,未参与此项测试
  • Kysely/Knex:作为查询构建器,不提供关系管理功能,未参与此项测试

ops.png

为何 Orchid ORM 性能更优

Orchid ORM 进行了诸多细微的优化,并且为达成目标执行的查询数量更少。

Prisma 依托于 Rust 服务器,node.js 与 Rust 服务器之间的通信效率较低,而且在加载关联关系时会使用独立的查询。不过,在过去的几个月里,它已得到了显著的优化。

Sequelize 在尝试加载关联关系时,会生成庞大且低效的 SQL 查询。

像 SequelizeTypeORMMikroORM 等一些 ORM 会执行映射到类实例的操作:首先从数据库中加载数据,然后利用这些数据来构造类实例,并在实例化过程中执行某些逻辑。随后,在将数据编码为 JSON 以进行响应之前,类需要转换回简单的 JavaScript 对象。

现状与局限性

尽管已经编写了数千个测试,但仍可能存在一些漏洞。若您遇到问题,请提交问题报告。

目前,除了 PostgreSQL 之外,暂无计划支持其他数据库。PostgreSQL 是一个功能极为丰富的数据库,具备众多扩展,几乎能够满足所有需求。例如,PostGIS 地理信息扩展;可作为 ElasticSearch 替代品的扩展;可替代 SQS、RabbitMQ、BullMQ 等工具的消息队列扩展,以及更多其他功能。相较于耗费数月时间去支持功能有限的数据库,更令人期待的是进一步深入利用 PostgreSQL 生态系统,让这款对象关系映射(ORM)工具变得更加强大。

您可以在连接多个数据库的应用程序中使用 Orchid ORM,但它无法管理跨数据库的表关系。

在执行 create(创建)、update(更新)、delete(删除)操作的返回子句中,无法选择关联关系数据。

目前,已有支持各种实用扩展的计划正在推进中。不过,在这些扩展实现之前,若需使用相关功能,您需要借助原生 SQL 语句来完成。Orchid ORM 的设计允许您结合其现有功能,并在必要之处添加少量自定义 SQL 语句。

Orchid ORM 不支持范围数据库类型以及复合自定义类型。

对于 JSON 列的搜索功能,目前仅提供了一些非常基础的方法,后续还需要进一步完善。