面向开发者的实用工具、库与效率技巧,写代码更快更爽。投稿 @BDHT1
#开发者 #编程工具 #效率 #程序员 · 开源总站 @GitHubTrendingHub
N+1 检测器如何找回被 ORM 藏起的代码行
作者在给 Node 写运行时 N+1 查询检测器时发现,检测本身很快完成,但定位到具体触发代码行却花了更久。检测器通过插桩数据库驱动工作,当同一查询形状在单次请求内多次执行时,会报告触发位置的文件和行号。最初在普通驱动上运行良好,但指向使用 Drizzle 的应用时,报告变成了「unknown call site」——查询被检测到、计数了,却无法归因到任何代码。
修复后同一脚本在 PostgreSQL 16、Drizzle 0.45.2、postgres.js 3.4.9 上从「unknown call site」变为准确的 at drizzle-test.mjs:35。作者还提醒一个陷阱:postgres.js 中 .values() 会执行查询,但 Drizzle 中 db.insert(t).values({...}) 只是构建 insert,误判会静默丢失 insert 的归因。工具是 nplusone,MIT 协议,零运行时依赖,NODE_ENV=production 时自动关闭。
#开发者 #工具 #Nodejs #Nplusone #Drizzle #TypeORM #PostgreSQL #性能分析
@DevToolboxHub
作者在给 Node 写运行时 N+1 查询检测器时发现,检测本身很快完成,但定位到具体触发代码行却花了更久。检测器通过插桩数据库驱动工作,当同一查询形状在单次请求内多次执行时,会报告触发位置的文件和行号。最初在普通驱动上运行良好,但指向使用 Drizzle 的应用时,报告变成了「unknown call site」——查询被检测到、计数了,却无法归因到任何代码。
作者第一反应是帧过滤器过于激进,跳过了 node_modules 和 node:internal 等框架帧。打印完整调用栈后发现,一次简单的 db.select() 查询产生了 12 帧,没有一帧属于应用代码。过滤器没问题,是信息本身不存在了。
原因在于 Drizzle 的查询是惰性 thenable。db.select().from(items).where(...) 并不执行任何操作,只是构建对象。真正执行发生在有人调用 .then() 时,而 await 时调用 .then() 的是 JavaScript 运行时,不是你的代码。此时你的函数早已返回,帧已从栈中消失。TypeORM 没有这个问题,因为 repo.find() 是你直接调用的 async 函数,Node 会在 await 边界保留异步栈轨迹。
解决方案是:在查询构建期间捕获调用位置,用 AsyncLocalStorage 携带到执行时刻,让驱动级插桩优先使用这个环境值而非栈回溯。调用位置在 db.select() 时捕获一次,随构建器链式传递,直到执行方法触发。最终 SQL 来自驱动,行号来自 ORM。
修复后同一脚本在 PostgreSQL 16、Drizzle 0.45.2、postgres.js 3.4.9 上从「unknown call site」变为准确的 at drizzle-test.mjs:35。作者还提醒一个陷阱:postgres.js 中 .values() 会执行查询,但 Drizzle 中 db.insert(t).values({...}) 只是构建 insert,误判会静默丢失 insert 的归因。工具是 nplusone,MIT 协议,零运行时依赖,NODE_ENV=production 时自动关闭。
#开发者 #工具 #Nodejs #Nplusone #Drizzle #TypeORM #PostgreSQL #性能分析
@DevToolboxHub
