mysql动态sql必须用prepare+execute配合?占位符绑定数据值,表名列名等标识符须白名单校验;用户变量传参需显式赋值,避免作用域与类型陷阱;权限和性能开销需专项优化。

MySQL存储过程里CONCAT拼接SQL就是高危操作
直接用CONCAT把用户输入塞进动态SQL里,等于把钥匙交给攻击者。哪怕加了TRIM或REPLACE,也拦不住' OR 1=1 -- 这类绕过——因为字符串拼接发生在SQL解析前,预处理机制根本没机会介入。
常见错误现象:SET @sql = CONCAT('SELECT * FROM users WHERE name = ''', in_name, '''');,传入in_name = 'admin'' OR ''1''=''1'就完蛋。
- 必须改用
PREPARE+EXECUTE配合占位符?,让MySQL服务端做参数绑定 - 所有用户可控的值(包括表名、列名、排序字段)都不能走
?——它们不属于“数据参数”,得用白名单校验+CONCAT兜底 -
EXECUTE stmt USING @var1, @var2;里的@var1必须是用户态变量,不能是存储过程参数直接代入(否则仍可能被污染)
哪些地方能用?占位符,哪些绝对不能
?只对**数据值**有效,MySQL明确不支持用它代替标识符(表名、列名、数据库名)。试图写SELECT * FROM ?会报错ERROR 1064 (42000)。
使用场景分两类:
- 安全可用:WHERE条件值、INSERT的VALUES、UPDATE的SET右侧表达式(如
UPDATE t SET col = ? WHERE id = ?) - 必须拦截:表名(
FROM ?)、列名(ORDER BY ?)、函数名(SELECT ?(col))、LIMIT偏移量(LIMIT ?, ?中第一个?合法,第二个不行) - 替代方案:对标识符做严格白名单检查,比如
IF in_table NOT IN ('users', 'orders') THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Invalid table'; END IF;
USING子句传参时,变量生命周期和类型陷阱
执行EXECUTE stmt USING @a, @b;前,@a和@b必须已存在且有值。存储过程参数in_name不能直接出现在USING里——MySQL会报ERROR 1318 (42000)。
容易踩的坑:
- 变量作用域:用户变量
@xxx跨EXECUTE依然有效,但存储过程退出后就消失;别依赖它在多次调用间保持状态 - 类型隐式转换:如果
@a是字符串但字段是INT,MySQL可能静默转成0,查不到数据却无报错 - NULL处理:传
NULL进USING,对应位置的WHERE条件变成col = NULL(永远为FALSE),得显式写IS NULL逻辑
实操建议:SET @name_param = in_name; SET @id_param = in_id; EXECUTE stmt USING @name_param, @id_param;
动态SQL性能和权限配置常被忽略的点
用PREPARE生成的语句在连接级缓存,但存储过程每次调用都会重编译——高频调用下,比静态SQL多3~5倍解析开销。
权限方面,执行动态SQL需要比普通查询更细的控制:
- 调用者必须有目标表的
SELECT/INSERT/UPDATE权限,且权限检查发生在EXECUTE时刻,不是定义存储过程时 - 如果存储过程用
DEFINER模式,注意SQL SECURITY DEFINER会让执行者获得定义者的权限,可能越权访问 - 测试时用
SHOW WARNINGS;确认是否触发了Unsafe statement警告(尤其涉及临时表或非确定函数)
真正麻烦的是标识符白名单维护——业务加新表就得改存储过程代码,这里没有银弹,只能靠配置表+校验函数兜底。











