in子句不支持运行时解析字符串为值列表,必须动态生成与参数数量一致的占位符(如?, ?, ?)并逐一绑定,禁用字符串拼接;空列表需处理为where 1=0,大列表应分批或改用临时表。

因为 IN 子句本身不接受“运行时解析字符串为值列表”,拼接出来的 "1,2,3" 在 SQL 层就是单个字符串字面量,不是三个独立参数——数据库根本不会拆它,但攻击者能借机注入任意语句。
IN 后面不能跟一个拼出来的字符串
你写的 "SELECT * FROM users WHERE id IN (" + idsStr + ")",最终发给数据库的是一条完整字符串。比如 idsStr = "1, 2, (SELECT password FROM admins)",数据库就真去执行子查询。
- MySQL、PostgreSQL、Oracle 都不支持把逗号分隔的字符串自动转成值列表
- 拼出来的
"'a','b','c'"是字符串,不是三个VARCHAR参数;"1,2,3"是字符串,不是三个INTEGER参数 - 类型错配还会导致索引失效(如字段是
INT,却传带引号的字符串)或直接报错(PostgreSQL 强类型)
预处理语句的 ? 占位符不能“代表多个值”
每个 ? 只绑定一个值,WHERE id IN (?) 永远只等价于 id = ?,不是 IN 列表。必须让占位符数量和参数数量严格一致。
- 正确做法:先算出数组长度,生成对应数量的
?,再整体绑定——例如[1,5,9]→"IN (?,?,?)"→execute(sql, [1,5,9]) - Node.js +
pg要用ANY($1)+ 数组参数,不是IN ($1, $2, $3)手动展开 - Python +
psycopg2不支持%s直接展开列表,必须用','.join(['%s'] * len(ids))动态构造占位符串
空数组会导致 IN () 语法错误
这是最常被跳过的边界情况。多数数据库(MySQL、PostgreSQL、SQL Server)都不允许 WHERE id IN (),会直接报错,而不是返回空结果。
- 必须在代码里提前判断:
if ids is empty → WHERE 1=0或跳过整个条件 - ORM 如 Prisma 的
where: { id: { in: [] } }会自动处理为空条件,但手写原生 SQL 或 MyBatis 的<if></if>必须显式写判断 - MyBatis 中漏掉
<if test="ids != null and ids.size > 0"></if>,上线后第一个空请求就炸
大列表(如 10000 个 ID)不只是安全问题,更是性能雷区
IN 列表过大不会触发 SQL 注入,但会暴露另一类线上故障:SQL 包超长、解析耗时、优化器退化、连接堆积。
- 限制来源是
max_allowed_packet(MySQL 默认 4MB),不是“1000 个值”的硬规则 - IN 列表超过几千项时,MySQL 仍会做排序+二分查找,但网络传输、驱动解析、服务端内存开销陡增
- 更稳的方案是改用临时表
JOIN、分批查(每批 ≤ 500)、或用EXISTS+ 子查询替代
真正难的不是写出安全的 IN 查询,而是记住:每次构造动态 IN,都要同时处理三件事——参数个数动态对齐、空数组兜底、大列表降级策略。少一个,线上就可能出问题。











