MySQL动态SQL唯一合法路径是SET→PREPARE→EXECUTE三步闭环,stmt为句柄名非变量,@sql须为已赋值用户变量,表名列名需白名单校验后硬拼,?仅用于值参数且须用USING绑定。

PREPARE + EXECUTE 是唯一合法路径
MySQL 存储过程不支持直接执行字符串 SQL,写 EXECUTE @sql 或 SET @sql = '...'; EXECUTE @sql 必然报错——常见错误是 ERROR 1243 (HY000): Unknown prepared statement 或 ERROR 1318 (42000): Incorrect arguments to EXECUTE。必须走三步闭环:SET @sql = ... → PREPARE stmt FROM @sql → EXECUTE stmt → (可选)DEALLOCATE PREPARE stmt。
关键约束:
-
stmt是句柄名(任意合法标识符),不是变量,不能带@ -
@sql必须是已赋值的用户变量,不能是局部变量(DECLARE v_sql VARCHAR(...)) - 重复
PREPARE stmt会静默覆盖前一个句柄,可能导致分支间语句错乱
表名、列名等标识符必须白名单校验后硬拼
占位符 ? 只能用于值(如 WHERE id = ?),不能用于表名、列名、ORDER BY 字段或 LIMIT 偏移量。试图写 SELECT * FROM ? 会立刻触发 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 = ?')少个空格就语法错误
所有值参数必须用 USING 绑定,严禁字符串拼接
用户可控的值(WHERE 条件、INSERT VALUES、UPDATE SET 右侧)一律走 USING,由 MySQL 自动转义和类型处理。写 CONCAT("name = '", in_name, "'") 是高危操作——in_name = "admin' OR '1'='1" 会直接穿透。
使用 USING 的硬性要求:
- 变量必须是用户变量(
@var),不能直接用存储过程参数(in_name),得先赋值:SET @name_param := in_name; -
USING后变量个数必须与 SQL 中?个数严格一致 -
@var必须已显式赋值,NULL要写成SET @val = NULL;,不能直接USING NULL -
USING不支持表达式:EXECUTE stmt USING @id + 1或EXECUTE stmt USING NOW()都会报错
字符集、句柄命名与调试建议
拼接含中文的字段值时,确保 @sql 变量是 utf8mb4 编码,否则可能乱码或截断;CONCAT() 默认继承连接字符集,但跨库或临时表场景下容易出问题,建议显式转换:CONVERT(in_name USING utf8mb4)。
句柄命名建议按分支隔离,避免复用同一 stmt 名导致执行旧语句。虽然 DEALLOCATE PREPARE 不是强制要求(会话结束自动清理),但在长连接、高频调用场景下,不释放会累积句柄,最终触发 ERROR 1436 (HY000): Thread stack overrun。
调试时务必加一句:SELECT @sql; 打印拼接结果,人工核对空格、引号、反引号、保留字(如 order、group)是否被正确包裹——这是最常被跳过的一步,也是多数语法错误的根源。


















