参数化查询是防sql注入唯一可靠方式:mysql用?占位符(需数组传参)、postgresql用$1形式,表名/字段名等结构部分须白名单校验后硬编码,不可参数化。

必须用参数化查询,不能拼接字符串——这是防 SQL 注入唯一可靠的方式。 手动转义、正则过滤、前端校验全都不足以替代它。
mysql2.query() 的 ? 占位符怎么用才安全
mysql2(推荐替代老版 mysql)的 query() 方法支持 ? 占位符,底层自动做类型绑定和引号包裹,完全隔离 SQL 结构与数据。
- ✅ 正确写法:参数必须是数组,哪怕只有一个值,
query('SELECT * FROM users WHERE id = ?', [req.query.id]) - ❌ 错误写法:直接拼接字符串,
query(`SELECT * FROM users WHERE id = ${req.query.id}`)—— 任何输入都可能触发' OR 1=1 --类攻击 - ⚠️ 注意:占位符只支持值,不支持表名、字段名或
ORDER BY子句;这些需通过白名单校验后硬编码 - ? 复杂条件可配合
express-validator提前校验字段合法性,再动态拼接 SQL 片段(如WHERE status IN (?)→[['active', 'pending']])
PostgreSQL 用户该用 $1 还是 ?
pg 驱动不认 ?,必须用数字位置参数 、,且顺序严格对应传入数组。
- ✅ 正确:
client.query('SELECT * FROM logs WHERE level = $1 AND ts > $2', ['error', new Date(Date.now() - 3600e3)]) - ❌ 错误:
client.query('SELECT * FROM logs WHERE level = ?', ['error'])→ 报错error: bind message supplies 1 parameters, but prepared statement "" requires 0 - ⚠️ 注意:pg 默认开启预编译(prepared statement),首次执行会缓存执行计划,后续同结构查询更快;但若 SQL 模板频繁变动(如动态字段),反而可能增加开销
node-mssql 的 @param 名称参数为什么更易读但要小心
SQL Server 场景下,node-mssql 支持命名参数(@username),语义清晰,适合复杂查询,但初始化成本略高。
- ✅ 推荐流程:
ps.input('username', sql.NVarChar(50))→ps.prepare('SELECT * FROM users WHERE username = @username')→ps.execute({ username: req.body.username }) - ⚠️ 容易踩的坑:忘记调用
ps.unprepare()会导致连接池资源泄漏;大量短生命周期查询建议改用request.input().query()简化流程 - ⚠️ 安全提示:库会检测参数名中是否含
--、/*等注入特征,一旦发现直接抛EINJECT错误,不是静默忽略
真正难的不是写对一行 query(),而是确保所有分支路径(比如 if/else 中的不同 SQL 拼接、日志记录里的原始 SQL 输出、错误信息里暴露的 query 字符串)都没漏掉参数化。只要有一处松动,整个防护就形同虚设。











