安全实现mysql动态in查询只能用find_in_set(需校验纯数字逗号串)或临时表方案,严禁字符串拼接,否则必然导致sql注入。

IN查询不能直接用字符串拼接,否则会SQL注入
MySQL存储过程里想实现 WHERE id IN (1,2,3) 这类动态条件,很多人第一反应是把参数拼成字符串再 CONCAT 进 SQL。这是危险操作:只要传入的参数未经校验,就可能执行任意 SQL。
根本原因在于,IN 后面不是表达式位置,而是语法结构;你无法用变量或用户变量代替整个括号列表(比如 WHERE id IN (@ids) 是无效语法)。
- MySQL 不支持将逗号分隔字符串自动展开为
IN列表 -
PREPARE + EXECUTE能绕过,但若拼接逻辑没做白名单/类型校验,照样被注入 - 即使只传数字,也要防
1 OR 1=1这类绕过
安全做法:用 FIND_IN_SET 或临时表替代 IN 列表
真正可落地、免拼接、防注入的方案只有两个,且都依赖参数本身是「已知安全值」:
-
FIND_IN_SET(id, @safe_comma_list):要求@safe_comma_list是纯数字逗号串(如'1,2,5'),且id字段必须是整型——因为FIND_IN_SET内部按子字符串匹配,对字符串字段可能误判(如'12'会被'1'匹中) - 更稳妥的是建临时表:
CREATE TEMPORARY TABLE _in_ids (id INT PRIMARY KEY),然后用循环或批量INSERT把参数逐个写入,最后JOIN或IN (SELECT id FROM _in_ids) - 如果参数来自外部输入,务必先用
CAST(... AS UNSIGNED)或正则过滤(REGEXP '^[0-9,]+$')校验格式,再拆分
别碰 PREPARE + CONCAT,除非你控制全部输入源
虽然下面这段代码语法上能跑通,但它在绝大多数业务场景下等于敞开大门:
SET @sql = CONCAT('SELECT * FROM users WHERE id IN (', in_id_list, ')');
PREPARE stmt FROM @sql;
EXECUTE stmt;
问题不在于 PREPARE 本身,而在于 in_id_list 如果来自用户输入(比如 HTTP 参数、日志字段、前端表单),就完全失去防护能力。
- 哪怕加了
REPLACE(in_id_list, "'", "''")也防不住绕过(如用十六进制、注释符、空字节) - MySQL 的
QUOTE()函数只能包裹单个值,无法处理逗号列表 - 唯一能用的前提:该参数是内部系统生成的、固定枚举值(如状态码列表
'1,2,4'),且全程不经过任何用户可控路径
INOUT 参数不适合传 IN 列表,别混淆用途
有人试图用 INOUT 参数接收一个字符串,再在过程里把它当 IN 列表用——这属于概念错配。
-
INOUT只是让参数既可读又可写,它不改变 SQL 解析时机;你仍然无法把变量名塞进IN (...)的括号里 - 它适合做计数器、累计值、状态标记(如
SET counter = counter + 1),而不是构造动态条件 - 真要动态
IN,请回到上一条:要么用FIND_IN_SET加强校验,要么走临时表,别寄希望于参数模式“变魔术”
最常被忽略的一点:临时表方案看似重,但在高并发下反而比字符串拼接更稳定——因为不用反复编译 SQL,也不依赖客户端传入格式。只要注意 DROP TEMPORARY TABLE 放在过程末尾,就不会堆积。











