2024年12月19日

Prisma ORM 的五大误区

了解 Prisma ORM 五大常见误区背后的真相。在本文中,我们将揭穿这些误区,探究其根源,并趣味性地辨别事实与虚构。

Top 5 Myths about Prisma ORM

误区1:Prisma ORM 速度慢

我们于 2021 年首次发布 用于生产环境的 Prisma ORM 时,遵循了“先让它工作,再让它正确,最后让它快速”的方法。这意味着 Prisma ORM 的初始版本并没有特别针对速度进行优化。

然而,自那以后,我们投入了大量精力进行性能优化,几乎在每个版本中都发布了性能改进。

我们还创建了开源的 ORM 基准测试,比较了 TypeScript 生态系统中三种最流行的 ORM,发现 Prisma ORM 的性能与其他 ORM 相似,有时甚至更快。

几乎每个版本都有重大的性能改进

Prisma ORM 一直遵循稳定可靠的三周发布周期。如果你查看 prisma/prisma 仓库的 发布页面,你会注意到几乎每个版本都带有一些性能改进——无论是特定 SQL 查询的优化(如 5.11.0, 5.9.0, 5.7.0, 5.4.0, 5.2.0, 5.1.0, … 中所示),引入新的批量查询,如 createManyAndReturn(在 5.14.0 中),将 冷启动速度提高了 9 倍(在 5.0.0 中),还是引入了 对原生基于 JS 的驱动的支持(在 5.4.0 中)。

我们还在将基于 Rust 的查询引擎从 Rust 重写到 TypeScript,以节省语言边界之间序列化的开销,并期望这次更改能带来显著的性能改进。

Prisma ORM 允许你选择最佳的 JOIN 策略

对于使用 Prisma ORM 的开发者来说,另一个巨大的进步是能够为其关系查询选择最佳的 JOIN 策略

原则上,当你需要查询通过 外键 关联的多个表中的数据时,有两种不同的方法:

数据库级别:在单个查询中使用 JOIN 关键字

使用这种方法,你向数据库发送一个使用 SQL JOIN 关键字的单一查询,并让数据由数据库直接 连接

Blog image

应用级别:发送多个查询并在应用程序中进行连接

在应用级别进行连接时,你向数据库发送针对单个表的多个查询,然后自己在应用程序中 连接 数据。

Blog image

何时使用哪种?

根据你的用例、数据集、模式以及其他几个因素,一种策略可能比另一种更具性能。应用级别的连接方法也称为 连接分解 ,常用于高性能环境中。

许多高性能网站使用连接分解。你可以通过运行多个单表查询而不是多表连接来分解连接,然后R在应用程序中执行连接。

高性能 MySQL, 第二版 | O'Reilly

在 Prisma ORM 5.7.0 之前,Prisma ORM 总是使用应用级别的 JOIN 策略。然而,随着 5.7.0 版本的发布,我们现在允许你根据你的用例选择最佳的 JOIN 策略,确保你的查询始终能获得最佳性能。

ORM 基准测试:无重大性能差异

经过所有这些改进,我们想知道 Prisma ORM 在性能方面与其他 ORM 库相比处于什么位置。因此,我们创建了透明的基准测试,比较了 TypeORM、Drizzle ORM 和 Prisma ORM 的查询性能。

基准测试仓库是开源的,我们邀请所有人重现结果并与我们分享。

Blog image

那么,基准测试显示了什么?

总结:根据我们收集的数据,无法得出结论说某个 ORM 总是 比其他 ORM 表现更好。相反,它取决于具体的查询、数据集、模式以及执行查询的基础设施。

你可以在这里阅读更多关于基准测试的设置、方法和结果:性能基准测试:比较 TypeScript ORM 和数据库的查询延迟

使用 Prisma Optimize 加快你的查询速度

运行基准测试的一个重要发现是,无论使用何种工具,都可以编写快速和慢速的查询。这意味着,最终确保数据库查询快速的重担实际上在于开发者自身。

Blog image

为了确保使用 Prisma ORM 的开发者能尽可能地加快查询速度,我们最近推出了 Prisma Optimize —— 一个分析你使用 Prisma ORM 发送给数据库的查询,并提供改进建议的工具。

误区2:无法使用低级数据库功能

Prisma ORM 作为一种 ORM,天生就提供 SQL 的更高层抽象,以提高与数据库交互时的生产力、信心和整体开发者体验。

这种更高层次的抽象体现在 人类可读的 Prisma schema(用于描述数据库结构)和 直观的 Prisma Client API(用于查询数据库)中。

然而,鉴于抽象有时也会使访问底层技术功能(对于 Prisma ORM 而言:数据库)变得不可能,因此需要一个适当的“逃生舱”来降级到更低的抽象层级。

因此,为了不牺牲在更高级场景或边缘情况可能需要的重要功能,Prisma ORM 为开发者提供了方便的回退机制,以访问数据库的底层功能。

定制化迁移允许开发者使用任何 SQL 功能

虽然不可能在 Prisma schema 中表示数据库可能拥有的 所有 功能,但你仍然可以通过定制由 Prisma Migrate 生成的迁移文件来利用这些功能。

为此,你只需在创建新迁移时使用 --create-only 标志,并在将其应用到数据库之前进行编辑。

使用定制化迁移,你可以自由地操作数据库 schema,同时确保所有更改都由 Prisma Migrate 执行并记录在其迁移历史中。

在 Prisma ORM 中编写类型安全的 SQL

在查询方面,开发者有两种主要方式可以回退到原始 SQL,并编写无法使用高级查询 API 表达的查询。

TypedSQL:使原始 SQL 类型安全

Prisma ORM 现在为你提供了两全其美的方案:为大多数查询提供方便的高级抽象,以及为原始 SQL 提供灵活、类型安全的逃生通道。

考虑这个你可能需要在应用程序中编写的原始 SQL 查询示例:

生成步骤完成后,你将能够通过 Prisma Client 中新的 $queryRawTyped 方法使用 conversionByVariant 查询。

在我们的博客上了解更多: TypedSQL 发布:使用 Prisma ORM 让你的原始 SQL 查询类型安全

使用 Kysely SQL 查询构建扩展

另一个替代方案是使用 Kysely 的 Prisma Client 扩展,它允许开发者使用其 TypeScript API 构建 SQL 查询。例如,使用 Kysely 扩展,你可以像这样使用 Prisma 编写 SQL 查询:

这使你无需离开 TypeScript 和 Prisma ORM 即可编写高级 SQL 查询。

趣事:Kysely 的核心维护者 Igal 最近加入了我们的 Prisma 团队 😄

误区3:Prisma ORM 底层使用 GraphQL

根据你在 Prisma 社区活跃的时间长短,这可能会让你感到惊讶:Prisma 曾经是一个名为 Graphcool 的 GraphQL 后端即服务提供商。

Blog image

2018 年,Graphcool 品牌重塑为 Prisma,并从 API 层级降到数据库层级,进一步“下放抽象”。

Prisma 的第一个版本(在成为 ORM 之前)是位于你的 API 服务器和数据库 之间 的 CRUD GraphQL 层。

在此阶段,Prisma 1 提供的主要价值是方便的数据建模、迁移查询,所有这些都是通过 GraphQL 完成的。

为了简化 Prisma 的使用并避免要求用户设置和维护一个完全独立的服务器,我们用 Rust 重写了 Prisma 的 GraphQL 引擎,使其可以通过 npm install 下载为二进制文件。

查询引擎作为应用程序服务器上的一个 side-car 进程运行一个 GraphQL 服务器。开发者使用 Prisma Client 与其交互并用 TypeScript 编写查询。这是 Prisma ORM 的初始架构。

从那时起,我们对架构进行了无数次优化。最显著的是,我们引入了 N-API 用于 Rust 和 TypeScript 之间的通信,用自定义的 基于 JSON 的传输协议 替换了 GraphQL,支持使用 JS 原生数据库驱动 等等!

如今,Prisma ORM 中已没有任何 GraphQL 的痕迹——我们也不会止步于此,我们将继续改进 Prisma ORM 的架构。我们的下一步是将负责生成 SQL 的查询引擎从 Rust 迁移到 TypeScript,使 Prisma ORM 更加高效。

误区4:Prisma Client 必须位于 node_modules

开发者对 Prisma ORM 的一个常见误解是,生成的 Prisma Client 库 必须 位于 node_modules 中。

然而,node_modules 只是 默认 位置,旨在提供熟悉的开发者体验并实现简单的导入。

该位置可以通过在 generator 块上提供自定义 output 路径来轻松定制。

在这种情况下,你需要调整 import 语句,并从文件系统导入 Prisma Client。考虑到上面的示例,现在的 import 将会是这样:

这在单体仓库(monorepo)或其他特殊环境中非常有用,因为在这种情况下,将 Prisma Client 生成到 node_modules 中可能会导致问题。

误区5:Prisma 与无服务器/边缘计算不兼容

当 Prisma ORM 设计时,无服务器(Serverless)和边缘(Edge)部署仍是早期的新兴技术。自那时起,它们已成为许多开发团队依赖的流行部署模型。

Prisma ORM 最初的架构,包括查询引擎二进制文件和内部 GraphQL 服务器,并未针对无服务器环境进行优化,存在许多问题:

  • 基于 GraphQL 的传输协议导致冷启动缓慢。
  • 无法使用现代数据库提供商(如 Neon 和 PlanetScale)的无服务器驱动;这完全阻止了在边缘环境中使用 Prisma Client。
  • 查询引擎二进制文件导致包体积过大。
  • 如果本地机器与目标机器不同,需要声明 binaryTargets,增加了复杂性。

我们已经认识到所有这些问题,并随着时间的推移,实施了解决方案,大幅改善了 Prisma ORM 在无服务器环境中的开发者体验(DX)。

  • 冷启动已不再是问题,因为我们已从查询引擎内部移除了 GraphQL,并将冷启动速度提高了 9 倍
  • 无服务器(Serverless)和其他JS 原生数据库驱动(如 pg)现在可以通过驱动适配器与 Prisma ORM 一起使用。
  • 我们已将 Prisma ORM 的包大小减少到 1MB 以下,使其可以在主要边缘函数提供商(如 Cloudflare,其免费计划限制为 3MB)的免费计划中使用。
  • ……我们还在致力于进一步的改进:从 Rust 迁移到 TypeScript 将消除声明 binaryTargets 的需要,并总体上使 Prisma ORM 的部署比以往任何时候都更加顺畅。

帮助我们让 Prisma ORM 成为最棒的数据库库 💚

在 Prisma,我们高度重视社区提供的反馈!虽然过去关于 Prisma ORM 的一些误解可能属实,但我们听取了用户的意见,并一直在努力改善这些情况。

如果你对我们的开源治理方法感兴趣,请查看Prisma ORM 宣言

我们将继续努力,使 Prisma ORM 成为 TypeScript 生态系统中性能最佳、开发者体验(DX)最好的数据库库。请通过 GitHubDiscordX 告诉我们你还希望看到哪些改进 🙌

如果你对 Prisma ORM 感到兴奋,可以通过在开发者社区中看到这些误解时分享这篇文章来帮助我们澄清它们。此外,如果还有其他你想让我们揭穿的误区,请告诉我们

不要错过下一篇文章!

订阅 Prisma 新闻通讯

© . All rights reserved.