2026西湖龙井茶官网DTC发售:茶农直供,政府溯源防伪到农户家
每当有人发布对象关系映射(ORM)基准测试时,总会出现这样的讨论:“当然 Java 数据库连接(JDBC)更快,你测量的是抽象层的开销”。他们说得对,但只对了一半。没人指出的是,抽象层并非唯一的罪魁祸首——有时问题出在你身上,因为你让 N+1 查询问题在未被察觉的情况下溜走了。
我构建了 prismavsjdbc 项目,以便以受控方式验证这一观点。这并非一场关于谁胜谁负的基准测试,而是一个实验环境:相同的 PostgreSQL 16 数据库、相同的 5 万条任务数据集以及相同的业务场景,分别在两种技术栈上运行:一侧是 Node.js 24 长期支持版(LTS)+ TypeScript + Prisma 5,另一侧是 Spring Boot 3 + Java 21 长期支持版(LTS)+ JdbcTemplate。所分析的提交哈希为 2cd33e32bd29a1d4b46a26af0b56d6a912f5e4f5,标签为 best-effort-editorial-final。
我所主张的核心论点是:查询结构、结构化查询语言(SQL)/请求次数以及 N+1 问题,比“ORM 与原始 SQL”这类口号更能解释性能差异。当你优化查询结构时,两种技术栈的性能都会提升;若不优化,两者都会让你付出代价。
那个险些让我得出错误结论的问题
该实验的初版存在一个明显的陷阱,尽管我起初并未察觉。它将最便捷的 Prisma 实现方式(使用 include 获取关联数据)与 JDBC 中的手动联结查询进行对比。结果可想而知:JDBC 测得每次请求执行 1 条 SQL 语句,而符合惯用法的 Prisma 在 read-by-id 场景下测得每次请求执行 4 条 SQL 语句,延迟也反映了这一差异。
我险些发布的错误结论是:“Prisma 更慢,因为它发出了更多查询。”
正确的结论是:我在比较不同的查询结构。Prisma 的 include 会为每个关联关系触发独立的查询——这不是缺陷,而是其应用程序编程接口(API)文档中明确约定的行为。JDBC 之所以执行联结查询,是因为我特意这样编写代码。若不承认这一前提就直接比较二者,是不公平的。
这一认知摩擦彻底改变了整个实验设计:我需要在每种技术栈内部分设三个层级。
三个层级: naive(朴素)、idiomatic(惯用)、best-effort(尽力优化)
在 results/comparison.csv 文件中添加 level 列,是本项目中最重要的决策。缺少这一列,任何结果表格都会对读者构成误导。
- naive(朴素):尽可能直接的实现方式,完全未考虑性能因素。在两种技术栈中,这都包括故意引入的 N+1 查询问题——即在循环中对每个任务执行独立查询。
-
idiomatic(惯用):在每种技术栈中以正常、可维护的方式编写代码。Prisma 使用
include和_count,JDBC 则采用任何 Java 开发者在不纠结于微优化的情况下都会编写的联结查询。 -
best-effort(尽力优化):团队可接受的最紧凑代码,且不沦为 hack(取巧方案)。对于 Prisma,这意味着在涉及聚合操作的查询结构中,降级使用
$queryRaw。
在惯用法 Prisma 的 read-by-id 场景中,由于使用了 include,测得每次请求执行 4 条 SQL 语句。而采用 $queryRaw 的 read-by-id-best-effort 变体则将每次请求的 SQL 语句数降至 1 条——与 JDBC 使用的联结查询相同。该查询的 PostgreSQL 执行计划清晰明了:
-- read-by-id-best-effort:Prisma $queryRaw 与 JdbcTemplate 中使用相同的 SQL
select t.id, t.title, t.status, t.cre
免责声明:本文内容来自互联网,该文观点不代表本站观点。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,请到页面底部单击反馈,一经查实,本站将立刻删除。