参数化查询能防sql注入,因其将sql结构与用户数据严格分离:数据库先编译语句骨架,再把参数作为纯字面量填入,使' or 1=1 --等恶意输入无法改变语法逻辑;但表名、列名、order by等结构部分不可参数化,须白名单校验。

参数化查询不是“更安全的写法”,而是把 SQL 指令结构和用户数据彻底隔离的强制机制。没用它,就等于把数据库的执行权直接交到用户输入手里。
参数化查询为什么能防住 ' OR 1=1 -- 这类攻击
关键在执行流程的分阶段:先编译语句结构,再填入值。数据库在解析阶段就锁定了 SQL 的语法树,后续传入的参数只被当作纯数据处理,不会触发重解析。
比如 Python 的 psycopg2 执行:
cursor.execute("SELECT * FROM users WHERE email = %s", (user_input,))
即使 user_input 是 ' OR 1=1 --,最终送进数据库的也只是一个字符串值,不是语句片段。
- 数据库看到的是:准备好的语句
SELECT * FROM users WHERE email = $1,然后把' OR 1=1 --当作第 1 个参数的字面值塞进去 - 它不会去拼接、不会去解释这个字符串里有没有单引号或注释符
- 所以
WHERE email = 'OR 1=1 --'是一条合法但无意义的查询,不是逻辑绕过
queryRawUnsafe 这种 API 为什么一用就破防
像 Prisma 的 $queryRawUnsafe、Django 的 extra() 或原生 execute() 字符串拼接接口,本质是绕过参数化管道,把控制权还给字符串构造逻辑。
常见踩坑场景:
- 搜索功能中写
WHERE name LIKE '%${q}%',q是用户输入,哪怕加了%通配符,单引号仍可闭合上下文 - 动态排序字段写成
ORDER BY ${sortField},攻击者传id; DROP TABLE users--就直接执行多语句 - 误以为 “过滤了单引号” 就安全,但 PostgreSQL 支持 Unicode 引号、MySQL 支持反引号绕过、某些驱动对空字节或编码混淆不敏感
不同语言里参数化查询的典型写法差异
不是所有占位符都等价,底层行为取决于驱动是否真正走预编译路径。
- Python:
psycopg2用%s,sqlite3用?,两者都强制参数绑定;但若用.format()或 f-string 拼出完整 SQL 字符串再传入,就失效 - Node.js:
pg驱动必须用client.query('SELECT ...', [value]),若写成client.query(`SELECT ... '${value}'`),哪怕 value 经过转义也不保险 - Java:
PreparedStatement是标准,但若用Statement+String.format拼接,等于裸奔 - PHP:
PDO::prepare()+execute()安全;mysql_query()(已废弃)或mysqli::query()直接拼接则危险
真正容易被忽略的点是:参数化只保护“值”,不保护“结构”。表名、列名、ORDER BY 字段、LIMIT 偏移量这些无法参数化——它们必须来自白名单或严格校验的枚举,不能由用户输入直接决定。











