prepare本身不防注入,仅支持占位符;必须配合参数绑定、白名单校验标识符、正确使用@变量并管理其生命周期才能真正防御sql注入。

PREPARE 本身不是防注入的“开关”,它只是让动态SQL能用占位符——但只有配合正确的参数绑定方式和白名单校验,才能真正防住注入。直接用 CONCAT 拼接用户输入再 PREPARE,照样中招。
为什么 PREPARE + EXECUTE 不等于自动防注入
PREPARE 只负责把字符串编译成语句模板,它不检查内容是否安全。常见错误是:
- 把用户输入拼进 SQL 字符串里,再 PREPARE:
SET @sql = CONCAT('SELECT * FROM users WHERE name = ''', in_name, '''');
即使后面 EXECUTE stmt,恶意输入如 admin'' OR ''1''=''1 已经在拼接时生效。
- 试图用 ? 替代表名或列名:SELECT * FROM ? 会直接报错 ERROR 1064 (42000),因为 MySQL 明确禁止占位符用于标识符。
USING 子句必须用用户变量,不能直接传存储过程参数
EXECUTE stmt USING @var1, @var2; 要求 @var1 是会话级用户变量(以 @ 开头),且必须提前赋值。
常见踩坑点:
- 直接写 EXECUTE stmt USING in_name, in_id; → 报错 ERROR 1318 (42000)
- 忘记给 @ 变量赋值,或类型不匹配(比如 @id 是字符串但字段是 INT)→ 查询结果为空或隐式转换出错
- 在循环中复用同一组 @ 变量但没重置 → 上次值污染本次执行
正确做法:
SET @name_val = in_name;SET @id_val = in_id;PREPARE stmt FROM 'SELECT * FROM users WHERE name = ? AND id = ?';EXECUTE stmt USING @name_val, @id_val;
表名、列名、排序字段等标识符必须走白名单校验
? 占位符对数据值有效,对结构无效。凡是可能被用户控制的标识符,必须显式校验:
- 允许的表名只能是 'users'、'orders'、'logs' 等固定集合
- 排序字段只接受 'created_at'、'id'、'status'
- 列名不能来自 in_col 参数,得硬编码或查配置表
典型防御写法:
IF in_table NOT IN ('users', 'orders') THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Invalid table name';END IF;SET @sql = CONCAT('SELECT * FROM ', in_table, ' WHERE status = ?');
LIKE 查询的通配符必须包含在参数值里,不能拼进 SQL
错误写法:CONCAT('%', in_keyword, '%') 再塞进 SQL → 还是拼接,风险未解除
正确做法:把完整匹配模式作为参数传入
SET @pattern = CONCAT('%', in_keyword, '%');PREPARE stmt FROM 'SELECT * FROM users WHERE name LIKE ?';EXECUTE stmt USING @pattern;
? 绑定的是带 % 的字符串,MySQL 会原样当值处理,不会解析为语法。
实际开发中最容易被忽略的,是标识符校验和 @ 变量生命周期管理——这两处一松懈,PREPARE 就形同虚设。











