PREPARE + EXECUTE 是唯一可行路径:MySQL存储过程必须严格遵循PREPARE stmt FROM @sql → EXECUTE stmt USING @var → DEALLOCATE PREPARE stmt三步闭环,表名列名需白名单校验后拼接,值参数必须通过USING绑定,严禁字符串拼接。

PREPARE + EXECUTE 是唯一可行路径
MySQL 存储过程不支持直接执行字符串 SQL,写 EXECUTE @sql 或 SET @sql = '...'; EXECUTE @sql; 必然报错——典型错误是 ERROR 1243 (HY000): Unknown prepared statement 或 ERROR 1318 (42000): Incorrect arguments to EXECUTE。必须走完整三步:PREPARE stmt FROM @sql → EXECUTE stmt USING @var1, @var2 → DEALLOCATE PREPARE stmt。
常见误操作包括:
- 把
@sql当句柄名用,比如PREPARE @sql FROM @sql - 漏掉
PREPARE直接EXECUTE -
USING后变量没提前声明为用户变量(@var),比如直接写USING p_id - 重复使用同一句柄名
stmt,不同分支里多次PREPARE stmt,导致后一次EXECUTE stmt执行的是前一个分支拼的旧 SQL
表名、列名等标识符必须白名单校验后硬拼
? 占位符只适用于值,不能用于表名、列名、ORDER BY 字段、LIMIT 偏移量等语法结构。写 SELECT * FROM ? 或 ORDER BY ? 会立刻触发 ERROR 1064 (42000)。
正确做法是先校验再拼接:
- 查
INFORMATION_SCHEMA.TABLES确认表存在:SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = in_table_name - 列名建议白名单硬控:
IF in_col NOT IN ('id', 'name', 'status') THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Invalid column'; END IF; - 拼接时注意空格和引号:
CONCAT('SELECT ', in_col, ' FROM ', in_table, ' WHERE id = ?')少个空格就语法错误 - 拼完建议先
SELECT @sql;打印出来人工核对一次
所有数据值必须通过 USING 绑定,严禁字符串拼接
WHERE 条件、INSERT 的 VALUES、UPDATE 的 SET 右侧,只要涉及用户可控输入,就必须走 USING。写 CONCAT("name = '", in_name, "'") 是高危行为——in_name = "admin' OR '1'='1" 会直接穿透成注入语句。
USING 有硬约束:
- 后面只能跟用户变量(
@var),不能直接写存储过程参数(如in_name);必须先SET @name_param := in_name; -
?个数必须和USING后变量个数严格一致,多一个少一个都报错 - 如果
@val是NULL,生成的条件是column IS NULL,不是column = NULL——这个逻辑差异常被忽略 - 字符集要统一,尤其拼接含中文的字段值时,确保
@sql变量是utf8mb4编码,否则可能乱码或截断
句柄命名和 DEALLOCATE 的实际影响
PREPARE stmt FROM @sql 中的 stmt 是固定句柄名,不是变量,不能带 @。它只在当前会话有效,重复 PREPARE stmt 会静默覆盖前一个,不报错也不警告。
虽然不 DEALLOCATE 不会导致内存泄漏(会话结束自动清理),但以下情况必须小心:
- 多个分支(
IF ... THEN ... ELSE)都用同一个句柄名,EXECUTE stmt可能执行上一个分支准备的旧语句 - 更稳妥的做法是动态命名句柄:
SET @stmt_name = CONCAT('stmt_', UNIX_TIMESTAMP()); PREPARE @stmt_name FROM @sql;,但注意EXECUTE不支持变量句柄名,得用EXECUTE IMMEDIATE(MySQL 8.0.19+)或改用固定名加逻辑隔离 - 最简方案:每个分支用不同句柄名,比如
stmt_select、stmt_update,避免覆盖风险
真正容易被忽略的点是:标识符校验和 USING 绑定必须同时到位,缺一不可。拼对了表名却把值拼进 SQL 字符串,或者校验了列名却忘了 SET @val := param,都会让整个动态 SQL 失去安全性。


















