prepare + execute 是唯一合法路径:mysql存储过程不支持直接执行sql字符串,必须严格遵循prepare stmt from @sql → execute stmt → deallocate prepare stmt完整链路,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。必须走 PREPARE stmt FROM @sql → EXECUTE stmt → DEALLOCATE PREPARE stmt 这个完整链路,缺一不可。
常见错误现象:把 @sql 当成句柄名用,比如 PREPARE @sql FROM @sql;或漏掉 PREPARE 直接 EXECUTE;或用了 EXECUTE stmt USING @var 却没声明 @var。
-
stmt是句柄名(任意合法标识符),不是变量,不能带@ -
@sql必须是用户变量,且拼接完成后再传给PREPARE - 重复
PREPARE stmt会覆盖前一个,不报错但可能执行旧语句
表名、列名等标识符必须白名单校验后硬拼
? 占位符只对数据值有效,不能用于表名、列名、ORDER BY 字段、LIMIT 偏移量等语法结构。试图写 SELECT * FROM ? 或 ORDER BY ? 会立刻触发 ERROR 1064 (42000)。
正确做法是先做合法性校验,再用 CONCAT() 拼进 @sql:
- 查
INFORMATION_SCHEMA.TABLES确认表存在且属当前库:SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = in_table_name - 列名同理查
INFORMATION_SCHEMA.COLUMNS,或直接白名单: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后变量个数必须严格一致,多一个少一个都报错 -
@var是NULL时,生成的是column IS NULL,不是column = NULL——这个逻辑差异常被忽略 - 字符集要统一:
@sql变量建议显式设为utf8mb4,避免中文字段值乱码或截断
动态句柄名与分支隔离容易被忽略
多个 IF 分支共用同一个句柄名 stmt 是高危操作。比如分支 A 执行了 PREPARE stmt FROM @sql_a,分支 B 又执行 PREPARE stmt FROM @sql_b,但分支 C 误走分支 A 的逻辑却调用 EXECUTE stmt,结果执行的是 @sql_a。
稳妥做法:
- 每个分支独立命名句柄:
SET @stmt_name = CONCAT('stmt_', UNIX_TIMESTAMP(), '_', RAND()); PREPARE @stmt_name FROM @sql; - 但注意
EXECUTE不支持变量句柄名(MySQL 8.0.23+ 才支持EXECUTE IMMEDIATE),所以更常用的是:分支间用不同固定名(stmt_a,stmt_b)并确保不交叉调用 - 调试时加
SELECT @sql;打印最终 SQL,人工核对语法是否合法
真正麻烦的从来不是怎么拼,而是拼完之后谁在什么上下文里执行它——句柄生命周期、作用域、分支跳转,这些细节一错,结果就不可控。











