sql注入风险源于字符串拼接,预编译是唯一有效防御机制;参数绑定需按驱动规范使用占位符,多条件、in列表、null判断须分别处理;表名、列名等sql结构部分无法参数化,须白名单校验或枚举映射。

SQL注入风险就藏在字符串拼接里
只要用字符串拼接构造 WHERE 条件,哪怕只拼一个用户输入的 ID,就等于把数据库大门钥匙交到对方手上。预编译不是“更安全的写法”,而是唯一能切断 SQL 注入链路的机制。
MySQL/PostgreSQL 中 PreparedStatement 的绑定姿势
不同驱动对参数占位符处理一致,但语法细节容易错:
- Java JDBC 必须用
?占位,不能写:name或$1(那是 PostgreSQL 原生命令行或某些 ORM 的写法) - Python
psycopg2支持%s和%(name)s,但%s不是 Python 字符串格式化,是驱动识别的占位符——传str会报错,必须传tuple或dict - Node.js
pg模块用$1,$2,顺序必须和query()第二个参数数组严格对应,错一位就查不到数据或类型报错
WHERE 条件里多个参数怎么绑才不翻车
常见错误是把整个条件当一个参数传进去,比如 WHERE status = ? 绑了 "'active' OR 1=1"——预编译只保护值本身,不解析 SQL 结构。
- 每个独立条件字段都要单独占位:
WHERE user_id = ? AND status = ? AND created_at > ? - IN 列表不能直接绑一个数组,
WHERE id IN (?)只匹配单值;必须动态生成等长占位符:WHERE id IN (?, ?, ?),再按个数传参 - NULL 判断不能写
WHERE col = ?然后传null,得用IS NULL或IS NOT NULL显式写死
预编译不是万能的,这些地方它管不了
绑定参数只解决“值”的注入,表名、列名、排序字段、LIMIT 数值这些属于 SQL 结构部分,预编译完全不处理。
- 动态表名:必须白名单校验,比如
if (table === 'users' || table === 'orders') { ... },别试图用参数绑定 - ORDER BY 字段:不能
ORDER BY ?,要映射成枚举:const sortMap = { name: 'name', time: 'created_at' }; sql += `ORDER BY ${sortMap[sort]}` - LIMIT 数值:MySQL 允许
LIMIT ?,但 PostgreSQL 要求整数常量,得用字符串拼接——此时必须用parseInt()+ 范围检查(如Math.min(num, 100))
最易被忽略的是:ORM 自动用了预编译,不代表你写的原始 SQL 就安全。只要手写 query(`SELECT * FROM ${tableName}`),前面所有努力都归零。










