从 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 使用 LATERAL
连接和数据库级 JSON 聚合(例如通过 json_agg
)在 PostgreSQL 中以及在 MySQL 上使用相关子查询来实现新的 join
关系加载策略。
在以下部分中,我们将调查为什么 PostgreSQL 上的 LATERAL
连接和数据库级 JSON 聚合方法比传统的普通 JOIN 更有效。
使用 JSON 聚合防止查询结果中的冗余
使用数据库级 JOIN
时,有多种构造 SQL 查询的选项。让我们考虑一下上面 Prisma 架构的 SQL 表定义
要检索所有带有帖子的用户,您可以使用简单的 LEFT JOIN
查询
这是包含一些示例数据的结果可能的样子
请注意,在这种情况下,user_name
列存在冗余。连接的表越多,这种冗余情况只会变得更糟。例如,假设还有另一个 Comment
表,其中每个评论都有一个指向 Post
表中记录的 postId
外键。
以下是一个表示该情况的 SQL 查询:
现在,假设第一篇文章有多个评论:
在这种情况下,结果集的大小会随着连接的表数量呈指数级增长。由于这些数据会从数据库通过网络传输到应用程序服务器,因此可能会非常耗费资源。
Prisma 实现的 join
策略,通过在数据库级别使用 JSON 聚合解决了这个问题。
以下是 PostgreSQL 的一个示例,它使用 json_agg
和 json_build_object
来解决冗余问题,并以 JSON 格式返回每个用户的帖子:
这次的结果集不包含冗余数据。此外,数据结构方便地已经具有 Prisma Client 返回的形状,这节省了在查询引擎中转换结果的额外工作。
使用 Lateral JOIN 实现更高效的查询,支持分页和筛选
关系查询(像大多数其他查询一样)几乎从不从表中获取全部数据,而是带有额外的结果集约束,如筛选和分页。特别是分页,使用传统的 JOIN 可能会变得非常复杂,让我们看另一个例子。
考虑以下 Prisma Client 查询,它获取 10 个用户以及每个用户的 5 个帖子:
在编写原始 SQL 时,你可能会试图在子查询中使用 LIMIT
子句,例如:
但是,这不会奏效,因为内部的 SELECT
实际上并没有返回每个用户的五个帖子,而是返回总共两个帖子,这当然不是期望的结果。
使用传统的 JOIN,可以通过使用 row_number()
函数为结果集中的记录分配递增的整数来解决这个问题,从而可以手动执行分页计算。
然而,这种方法很快就会变得非常复杂,因此不适合构建分页关系查询。
维护、扩展和调试这些类型的 SQL 查询令人望而生畏,并且可能会耗费数小时的开发时间。
幸运的是,较新的数据库版本通过一种新的查询类型解决了这个问题:lateral JOIN。
可以通过使用 LATERAL
关键字来简化上述查询:
这不仅使查询更具可读性,而且数据库引擎也更可能能够优化查询,因为它能够更多地了解查询的意图。
结论
让我们回顾一下使用 Prisma 连接关系查询数据的不同选项。
过去,Prisma 仅支持应用程序级别的连接策略,该策略向数据库发送多个查询,并在查询引擎内部完成将数据合并和转换为预期 JavaScript 对象结构的所有工作。
使用普通的传统 JOIN,数据的合并将委托给数据库。但是,如上所述,存在数据冗余问题(结果集会随着关系查询中表的数量呈指数级增长),以及包含筛选和分页的查询的复杂性问题。
为了解决这些问题,Prisma ORM 在数据库级别实现了现代的 lateral JOIN,并结合了 JSON 聚合。这样,在数据库级别完成解决查询并将数据转换为预期 JavaScript 对象结构所需的所有繁重工作。
试用并分享你的反馈
我们希望你尝试新的关系查询加载策略。请告诉我们你的想法,并与我们分享你的反馈!
不要错过下一篇文章!
注册 Prisma 新闻通讯