2024年12月19日

关于 Prisma ORM 的 5 大误区

探索关于 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 关键字的查询,然后让数据库直接将数据 联接

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

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

何时使用哪种方法?

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

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

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

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

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

在进行了所有这些改进之后,我们想了解与其他 ORM 库相比,Prisma ORM 在性能方面的表现如何。因此,我们创建了透明的 基准测试,比较了 TypeORM、Drizzle ORM 和 Prisma ORM 的查询性能。

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

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

TLDR:根据我们收集的数据,无法断定某个 ORM 总是 比另一个性能更好。相反,这取决于具体的查询、数据集、Schema 以及执行查询的基础设施。

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

使用 Prisma Optimize 加速您的查询

运行基准测试的一个主要发现是,无论您使用什么工具,都有可能编写出快速或慢速的查询。这意味着最终,确保数据库查询快速的很多负担实际上都在开发人员自己身上。

为了确保使用 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 后端即服务提供商

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 的 wire protocol 取代了 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 与 Serverless/Edge 不兼容

Prisma ORM 设计之初,Serverless 和 Edge 部署仍是新兴技术。自那时起,它们已成为许多开发团队依赖的流行部署模式。

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

  • 由于基于 GraphQL 的 wire protocol 导致的冷启动慢。
  • 无法使用现代数据库提供商(如 Neon 和 PlanetScale)的 Serverless 驱动程序;这完全阻止了在 Edge 上使用 Prisma Client。
  • 由于查询引擎二进制文件导致的捆绑包体积大。
  • 如果本地机器与目标机器不同,需要声明 binaryTargets 增加了复杂性。

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

  • 冷启动不再是问题,因为我们从查询引擎内部 移除了 GraphQL,并将 冷启动速度提高了 9 倍
  • 借助驱动程序适配器,现在可以在 Prisma ORM 中使用 Serverless 和其他基于 JS 的原生数据库驱动程序(如 pg)。
  • 我们已将 Prisma ORM 的捆绑包大小减小到小于 1MB,使其可以在主要 Edge 函数提供商(如 Cloudflare,其免费计划限制为 3MB)的免费计划中使用。
  • ……我们还在努力进行进一步的改进:从 Rust 迁移到 TypeScript 将消除声明 binaryTargets 的需要,并整体上使 Prisma ORM 的部署比以往更加顺畅。

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

在 Prisma,我们高度重视从社区收到的反馈!尽管过去关于 Prisma ORM 的一些误解可能是真实的,但我们听取了用户的意见,并一直在努力改进相关情况。

如果您想了解更多关于我们开源治理方法的信息,请查看 Prisma ORM 宣言

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

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

不要错过下一篇文章!

订阅 Prisma 新闻通讯