2024年2月21日

Prisma ORM 现在允许您选择最佳连接策略(预览版)

从 SQL 数据库中获取多表相关数据可能会非常昂贵。Prisma ORM 现在允许您在数据库层面应用层面连接之间进行选择,以便您为关系查询选择最高效的方法。

Prisma ORM Now Lets You Choose the Best Join Strategy (Preview)

目录

Prisma ORM 新功能:选择最佳连接策略 🎉

对数据库层面连接的支持是 Prisma ORM 中最受欢迎的功能之一,我们很高兴地宣布,它现在作为另一种查询策略提供!

对于任何带有 include(或 select)的关系查询,现在在顶层有一个名为 relationLoadStrategy 的新选项。此选项接受以下两个可能值之一:

  • join(默认):使用数据库层面连接策略在数据库中合并数据。
  • query:通过向单个表发送多个查询,并在应用层合并数据来使用应用层面连接策略。

要启用新的 relationLoadStrategy,您首先需要将预览功能标志添加到 Prisma Client 的 generator 块中

注意relationLoadStrategy 仅适用于 PostgreSQL 和 MySQL 数据库。

完成此操作后,您需要重新运行 prisma generate 以使此更改生效,并在查询中选择关系加载策略。

这是一个使用新 join 策略的示例

请注意,由于 "join" 是默认值,relationLoadStrategy 选项在上面的代码片段中理论上也可以省略。我们在此处显示它仅用于说明目的。

joinquery — 何时使用?

现在有了这两种查询策略,您可能会想:何时使用哪种?

由于 Prisma ORM 在 PostgreSQL 上使用的横向、聚合 JOIN 以及在 MySQL 上使用的关联子查询,join 策略在大多数情况下可能更高效(后续部分将详细介绍)。数据库引擎非常强大,擅长优化查询计划。这种新的关系加载策略正是为了充分利用这一点。

但是,在某些情况下,您可能仍然希望使用 query 策略,即为每个表执行一个查询并在应用层面合并数据。根据数据集和模式中配置的索引,发送多个查询可能会更高效。对您的查询进行性能分析和基准测试对于识别这些情况至关重要。

另一个考虑因素是复杂连接查询所带来的数据库负载。如果由于某种原因数据库服务器上的资源稀缺,您可能希望将复杂连接查询(包含过滤和分页)所需的繁重计算转移到您的应用服务器,因为应用服务器可能更容易扩展。

总结

  • 在大多数情况下,新的 join 策略会更高效。
  • 在某些边缘情况下,query 可能会更高效,具体取决于数据集和查询的特性。我们建议您对数据库查询进行性能分析以识别这些情况。
  • 如果您想节省数据库服务器上的资源,并在可能更容易扩展的应用服务器中执行数据合并和转换的繁重工作,请使用 query

理解SQL数据库中的关系

现在我们已经了解了 Prisma ORM 的 JOIN 策略,接下来让我们回顾一下 SQL 数据库中关系查询通常是如何工作的。

关系的扁平与嵌套数据结构

SQL 数据库以扁平(即规范化)方式存储数据。实体之间的关系通过外键表示,这些外键指定了跨表的引用。

另一方面,应用开发人员通常习惯于处理嵌套数据,即可以任意深度嵌套其他对象的对象。

这是一个巨大的差异,不仅在于数据在磁盘和内存中的物理布局方式,还在于数据的心智模型和推理方式。

Blog image

关系数据需要为应用开发者进行“合并”

由于相关数据在数据库中是物理上分开存储的,因此需要将其在某个地方合并,以成为应用开发人员熟悉的嵌套结构。这种合并也称为“连接(join)”。

这种连接可以在两个地方发生:

  • 数据库层面:向数据库发送单个 SQL 查询。该查询使用 JOIN 关键字或关联子查询,让数据库在多个表之间执行连接,并返回嵌套结构。
  • 应用层面:向数据库发送多个查询。每个查询只访问一个表,然后查询结果在应用层进行内存合并。这曾是 v5.9.0 之前 Prisma Client 唯一支持的查询策略。

哪种方法更可取取决于所使用的数据库、数据集的大小和特性以及查询的复杂性。请继续阅读,了解何时推荐使用哪种策略。

底层原理是什么?

Prisma ORM 通过在 PostgreSQL 中使用 LATERAL 连接和数据库层面的 JSON 聚合(例如通过 json_agg),以及在 MySQL 中使用关联子查询,实现了新的 join 关系加载策略。

在以下部分中,我们将探讨为什么 PostgreSQL 上的 LATERAL 连接和数据库层面的 JSON 聚合方法比简单、传统的 JOIN 更高效。

使用 JSON 聚合防止查询结果中的冗余

在使用数据库层面的 JOIN 时,有多种构建 SQL 查询的选项。让我们考虑上面 Prisma schema 的 SQL 表定义

要检索所有用户及其帖子,您可以使用简单的 LEFT JOIN 查询

这是使用一些示例数据时可能的结果:

请注意本例中 user_name 列的冗余。连接的表越多,这种冗余就会越严重。例如,假设有另一个 Comment 表,其中每个评论都有一个指向 Post 表中记录的 postId 外键。

这是一个表示该情况的 SQL 查询

现在,假设第一篇帖子有多个评论

在这种情况下,结果集的大小随着连接表数量的增加呈指数级增长。由于这些数据需要通过网络从数据库传输到应用服务器,这可能会变得非常昂贵。

Prisma 实现的在数据库层面使用 JSON 聚合的 join 策略解决了这个问题。

这是一个 PostgreSQL 的示例,它使用 json_aggjson_build_object 来解决冗余问题,并以 JSON 格式返回每个用户的帖子。

这次的结果集不包含冗余数据。此外,数据结构已经方便地具备了 Prisma Client 返回的形状,这节省了查询引擎中转换结果的额外工作。

使用横向 JOIN 实现更高效的带分页和过滤的查询

关系查询(像大多数其他查询一样)几乎从不获取表中的所有数据,而是带有额外的结果集约束,如过滤和分页。特别是分页在使用传统 JOIN 时会变得非常复杂,让我们看另一个示例。

考虑这个 Prisma Client 查询,它获取 10 个用户以及每个用户的 5 篇帖子

当以原始 SQL 编写时,您可能会想在子查询中使用 LIMIT 子句,例如:

但是,这不起作用,因为内部的 SELECT 实际上并未返回每个用户五篇帖子 — 相反,它总共返回了两篇帖子,这显然不是我们想要的结果。

使用传统的 JOIN,可以通过使用 row_number() 函数为结果集中的记录分配递增的整数来解决,从而可以手动执行分页计算。

然而,这种方法会很快变得非常复杂,因此不适合构建分页关系查询。

维护、扩展和调试这类 SQL 查询令人望而生畏,可能耗费数小时的开发时间。

值得庆幸的是,较新的数据库版本通过一种新型查询解决了这个问题:横向 JOIN

上述查询可以通过使用 LATERAL 关键字进行简化

这不仅使查询更具可读性,而且数据库引擎也可能更能够优化查询,因为它能更好地理解查询的意图

结论

让我们回顾一下使用 Prisma 连接关系查询数据时的不同选项。

过去,Prisma 仅支持应用层面连接策略,该策略向数据库发送多个查询,并在查询引擎内部完成所有合并和转换为预期 JavaScript 对象结构的工作。

Blog image

使用简单、传统的 JOIN,数据合并将委托给数据库。然而,如上所述,存在数据冗余问题(结果集随着关系查询中表数量的增加呈指数级增长)以及包含过滤和分页的查询复杂性问题。

Blog image

为了解决这些问题,Prisma ORM 在数据库层面实现了现代的横向 JOIN 以及 JSON 聚合。这样,解决查询并将数据转换为预期 JavaScript 对象结构所需的所有繁重工作都在数据库层面完成。

Blog image

试用并分享您的反馈

我们非常希望您能尝试新的关系查询加载策略。请告诉我们您的想法,并与我们分享您的反馈

不要错过下一篇文章!

订阅 Prisma 简报

© . All rights reserved.