从 SQL 数据库的多个表中获取相关数据可能会非常耗时。Prisma ORM 现在允许您在数据库级别和应用程序级别连接之间进行选择,以便您可以为您的关系查询选择性能最佳的方法。
目录
Prisma ORM 新功能:选择最佳连接策略 🎉
支持数据库级别连接一直是 Prisma ORM 中最受欢迎的功能之一,我们很高兴地宣布它现在作为另一种查询策略可用!
对于任何包含 include(或 select)的关系查询,现在在顶层有一个新选项叫做 relationLoadStrategy。此选项接受以下两个可能值之一:
join(默认):使用数据库级别连接策略在数据库中合并数据。query:通过向各个表发送多个查询,并在应用程序层合并数据,使用应用程序级别连接策略。
要启用新的 relationLoadStrategy,您首先需要将预览功能标志添加到 Prisma Client 的 generator 块中
注意:
relationLoadStrategy仅适用于 PostgreSQL 和 MySQL 数据库。
完成此操作后,您需要重新运行 prisma generate 以使此更改生效,并在查询中选择关系加载策略。
这是一个使用新 join 策略的示例
请注意,由于 "join" 是默认值,所以在上面的代码片段中,relationLoadStrategy 选项在技术上也可以省略。我们在这里只是为了说明目的展示它。
join 与 query — 何时使用哪个?
现在有了这两种查询策略,您可能会想:何时使用哪个?
由于 Prisma ORM 在 PostgreSQL 上使用横向聚合 JOIN,在 MySQL 上使用相关子查询,join 策略在大多数情况下可能更高效(后续部分将有更多详细信息)。数据库引擎非常强大,并且擅长优化查询计划。这种新的关系加载策略正是体现了这一点。
然而,在某些情况下,您可能仍希望使用 query 策略对每个表执行一个查询并在应用程序级别合并数据。根据数据集和架构中配置的索引,发送多个查询可能性能更好。分析和基准测试您的查询对于识别这些情况至关重要。
另一个考虑因素可能是复杂连接查询所带来的数据库负载。如果由于某种原因数据库服务器上的资源稀缺,您可能希望将复杂连接查询所需的繁重计算(包含过滤器和分页)转移到您的应用程序服务器上,这可能更容易扩展。
TLDR
- 新的
join策略在大多数情况下会更高效。 - 在某些边缘情况下,根据数据集和查询的特性,
query可能会更高效。我们建议您分析数据库查询以识别这些情况。 - 如果您想节省数据库服务器上的资源,并在应用程序服务器上进行合并和转换数据的繁重工作(可能更容易扩展),请使用
query。
理解 SQL 数据库中的关系
现在我们了解了 Prisma ORM 的 JOIN 策略,让我们回顾一下关系查询在 SQL 数据库中通常是如何工作的。
关系的扁平与嵌套数据结构
SQL 数据库以扁平(即 规范化)方式存储数据。实体之间的关系通过外键表示,外键指定了跨表的引用。
另一方面,应用程序开发人员通常习惯于处理嵌套数据,即可以任意深度嵌套其他对象的对象。
这是一个巨大的差异,不仅体现在数据在磁盘和内存中的物理布局方式上,也体现在关于数据的心智模型和推理上。
关系数据需要为应用程序开发人员“合并”
由于相关数据在数据库中物理上是独立存储的,因此需要将其在某个地方合并,以形成应用程序开发人员熟悉的嵌套结构。这种合并也称为“连接”。
这种连接可以在两个地方发生
- 在数据库层面:向数据库发送单个 SQL 查询。该查询使用
JOIN关键字或相关子查询,让数据库执行跨多个表的连接并返回嵌套结构。 - 在应用程序层面:向数据库发送多个查询。每个查询只访问一个表,然后查询结果在应用程序层内存中合并。在
v5.9.0之前,这曾是 Prisma Client 支持的唯一查询策略。
哪种方法更可取取决于所使用的数据库、数据集的大小和特性以及查询的复杂性。继续阅读以了解何时建议使用哪种策略。
幕后发生了什么?
Prisma ORM 使用 PostgreSQL 中的 LATERAL 连接和数据库级别的 JSON 聚合(例如通过 json_agg)以及 MySQL 上的相关子查询来实现新的 join 关系加载策略。
在以下部分中,我们将探讨为什么 PostgreSQL 上的 LATERAL 连接和数据库级别的 JSON 聚合方法比传统的普通 JOIN 更高效。
使用 JSON 聚合防止查询结果冗余
当使用数据库级别的 JOIN 时,有几种构建 SQL 查询的选项。让我们考虑上面 Prisma 模式的 SQL 表定义
要检索所有用户及其帖子,您可以使用简单的 LEFT JOIN 查询
以下是使用一些样本数据的结果

在这种情况下,请注意 user_name 列的冗余。这种冗余会随着连接表的增多而变得更加严重。例如,假设有另一个 Comment 表,其中每个评论都有一个指向 Post 表中记录的 postId 外键。
以下是一个表示此情况的 SQL 查询
现在,假设第一个帖子有多个评论

在这种情况下,结果集的大小随着连接表的数量呈指数级增长。由于这些数据通过网络从数据库传输到应用程序服务器,这可能会变得非常昂贵。
Prisma 在数据库级别通过 JSON 聚合实现的 join 策略解决了这个问题。
以下是 PostgreSQL 的一个示例,它使用 json_agg 和 json_build_object 来解决冗余问题,并以 JSON 格式返回每个用户的帖子
这次的结果集不包含冗余数据。此外,数据结构方便地已经具有 Prisma Client 返回的形状,这节省了在查询引擎中转换结果的额外工作

使用横向 JOIN 实现更高效的分页和过滤查询
关系查询(像大多数其他查询一样)几乎从不获取表中的所有数据,而是带有额外的结果集约束,如过滤和分页。特别是分页在传统 JOIN 中会变得非常复杂,让我们看另一个示例。
考虑这个 Prisma Client 查询,它获取 10 个用户和每个用户 5 个帖子
当用原始 SQL 编写时,您可能会倾向于在子查询中使用 LIMIT 子句,例如
然而,这不起作用,因为内部的 SELECT 实际上并未返回每个用户五篇帖子 — 相反,它总共返回两篇帖子,这当然不是期望的结果。
使用传统的 JOIN,可以通过使用 row_number() 函数为结果集中的记录分配递增整数来解决,通过该函数可以手动执行分页计算。
但是,这种方法会很快变得非常复杂,因此不适用于构建分页关系查询。
维护、扩展和调试这类 SQL 查询令人望而生畏,可能会耗费数小时的开发时间。
幸好,较新的数据库版本通过一种新型查询解决了这个问题:横向 JOIN。
上述查询可以通过使用 LATERAL 关键字进行简化
这不仅使查询更具可读性,而且数据库引擎也可能更能够优化查询,因为它能更好地理解查询的意图。
结论
让我们回顾一下使用 Prisma 连接关系查询数据的不同选项。
过去,Prisma 只支持应用程序级连接策略,该策略向数据库发送多个查询,并在查询引擎内部完成所有合并和将其转换为预期 JavaScript 对象结构的工作
使用普通的传统 JOIN,数据合并将委托给数据库。然而,如上所述,存在数据冗余问题(结果集随着关系查询中表的数量呈指数级增长)以及包含过滤器和分页的查询的复杂性问题
为了解决这些问题,Prisma ORM 实现了现代化的横向 JOIN,并结合数据库级别的 JSON 聚合。这样,解决查询并将数据转换为预期 JavaScript 对象结构所需的所有繁重工作都在数据库级别完成
试用并分享您的反馈
我们希望您能尝试新的关系查询加载策略。请告诉我们您的想法,并与我们分享您的反馈!
不要错过下一篇文章!
订阅 Prisma 新闻通讯