返回
创建于
状态公开

这是一个非常棒的安全意识问题。简单来说,你使用的这种写法并不是普通的字符串拼接,而是利用了 Tagged Templates(标签模板字符串) 技术。

在 Prisma 中,db.$queryRaw 后面的反引号内容会被特殊处理,而不是直接交给数据库执行。


1. 它是如何工作的?

当你写下以下代码时:

typescript
1db.$queryRaw`SELECT GET_LOCK(${LOCK_NAME}, ${WAIT_TIMEOUT}) AS acquired`

JavaScript 引擎实际上会将它分解为两部分传给 Prisma:

  1. 模板字符串数组["SELECT GET_LOCK(", ", ", ") AS acquired"]
  2. 变量数组[LOCK_NAME, WAIT_TIMEOUT]

2. 参数化查询(Parameterized Query)

Prisma 接收到这两部分后,不会简单地把变量填进字符串里,而是会执行以下操作:

  1. 发送 SQL 骨架:向数据库发送带有占位符的指令,例如: SELECT GET_LOCK(?, ?) AS acquired
  2. 单独发送数据:将 LOCK_NAMEWAIT_TIMEOUT 作为独立的数据包发送。

3. 为什么这样就能防注入?

  • 身份隔离:数据库在解析 SQL 语法时,看到的只是 ? 占位符。它已经完成了对 SQL 指令的编译(Parsing)。
  • 无法改变逻辑:即便你的 LOCK_NAME 恶意写成 'a', 0); DROP TABLE users; --,数据库也只会把它当成一个极其长的、奇怪的字符串传递给 GET_LOCK 函数,而不会把它解释为另一条 SQL 命令。

4. 错误示范(千万不要这样做)

如果你直接用字符串模板拼接(不使用 Tagged Templates 的功能),就会产生注入风险:

typescript
1// ❌ 危险写法:先拼接成字符串,再传给 Prisma
2const query = `SELECT GET_LOCK('${LOCK_NAME}', 0)`; 
3await db.$queryRawUnsafe(query); // 这里的 Unsafe 意味着 Prisma 不会帮你处理注入

在这种情况下,如果 LOCK_NAME'); DROP TABLE Users; --,最终执行的 SQL 会变成: SELECT GET_LOCK(''); DROP TABLE Users; --', 0) 这会导致你的数据库表被删除。


5. Prisma 的保护机制总结

特性$queryRaw (反引号)$queryRawUnsafe
底层原理自动转化为参数化查询直接执行原始字符串
安全性(免疫 SQL 注入)(需手动过滤变量)
使用场景绝大多数场景动态拼接表名或列名时(需极度小心)

💡 一个小提醒

虽然变量是安全的,但 MySQL 的 GET_LOCK 对名字长度有限制(最大 64 个字符)。如果你的 LOCK_NAME 是动态生成的且超过这个长度,虽然没有注入风险,但 GET_LOCK 会返回 NULL 或报错。

你想了解如何利用 Prisma 的 db.$executeRaw 来处理那些不需要返回结果的写操作锁吗?