mysql存储过程不防sql注入,安全取决于写法:动态sql必须用prepare+execute+?绑定数据值;表名列名等标识符须白名单校验;静态sql优先;权限与变量管理不可忽视。

MySQL存储过程本身不防SQL注入,安全与否完全取决于你写法是否规范——只要用CONCAT拼接用户输入,哪怕只加一个单引号,就等于把数据库的门钥匙直接递出去。
动态SQL必须用PREPARE+EXECUTE+?绑定数据值
所有用户可控的值(如WHERE条件、INSERT的VALUES、UPDATE的SET右侧)都得走?占位符,由MySQL服务端做类型绑定和边界隔离。
- ✅ 正确写法:
SET @sql = 'SELECT * FROM users WHERE status = ? AND created_at > ?'; PREPARE stmt FROM @sql; EXECUTE stmt USING @status_val, @time_val; - ❌ 危险写法:
SET @sql = CONCAT('SELECT * FROM users WHERE name = ''', in_name, '''');——传入in_name = 'admin'' OR ''1''=''1'就直接绕过 -
USING后面只能跟用户变量(@xxx),不能直接写存储过程参数(如in_name),否则报错ERROR 1318 (42000) - 实操建议:先显式赋值
SET @name_param = in_name;,再EXECUTE stmt USING @name_param;
表名、列名、排序字段等标识符必须白名单校验
?在MySQL里只认“数据值”,对表名、列名、函数名、LIMIT偏移量这些语法结构完全无效。强行用会直接报错ERROR 1064 (42000)。
- ❌ 错误尝试:
SET @sql = 'SELECT * FROM ? WHERE id = ?';——第一个?不被支持 - ✅ 正确做法:白名单硬校验 +
CONCAT拼接,例如:IF in_table NOT IN ('users', 'orders', 'logs') THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Invalid table'; END IF; - 容易忽略的标识符场景:
ORDER BY ?、GROUP BY ?、SELECT col AS ?、甚至LIMIT ?, 10中的第一个参数(偏移量)——它们都不能用?
静态SQL天然安全,优先考虑不用动态SQL
如果查询逻辑固定(比如按用户名查用户),直接写WHERE username = in_name,MySQL会把参数当值处理,不解析为语法,根本不存在注入路径。
- 静态SQL无需
PREPARE/EXECUTE,无性能开销,也无变量生命周期陷阱 - 动态SQL仅在真正需要时使用(如多表可选、字段可变、排序字段可配置)
- 别为了“灵活性”硬上动态SQL——多数业务场景都能拆成几个静态过程组合调用
权限与变量作用域常被低估
即使SQL写法正确,权限和变量管理出错也会让防护形同虚设。
- 调用存储过程的账号不应有直接
SELECT表权限,只赋予EXECUTE该过程权限 -
DEFINER属性要明确指定低权限账号,避免以高权限用户身份执行 - 用户变量(
@xxx)在会话级共享,跨过程调用时可能被污染,务必每次显式赋值 -
DEALLOCATE PREPARE要配对调用,但别在循环里反复PREPARE/DEALLOCATE,影响性能
最危险的不是不会写动态SQL,而是以为用了存储过程就自动安全——只要拼接发生在SQL解析前,预处理机制就彻底失效。白名单校验、显式变量赋值、权限收敛这三步,漏掉任何一环,前面写的?都等于没写。











