PREPARE 语句准备的是可执行的SQL模板并缓存执行计划,仅接受字符串字面量或用户变量;表名列名须白名单校验后拼接,EXECUTE USING仅支持已赋值用户变量且数量顺序须与?严格匹配。

PREPARE 语句到底准备了什么?
PREPARE 并不编译 SQL,而是把字符串解析成可执行的语句模板,并缓存其执行计划(在支持该行为的 MySQL 版本中)。它只接受字符串字面量或用户变量(@variable),不能直接拼接表达式或列名。
- 必须用
SET @sql = CONCAT(...)构造完整语句字符串,再传给PREPARE - 表名、列名、排序字段等不能用
?占位符 —— 那是为值预留的,不是为标识符 - 执行前需确保
@sql内容是合法 SQL,否则EXECUTE会报错ERROR 1064 (42000)
怎么安全拼接表名和列名?
MySQL 不允许 ? 替换表名,所以必须手动拼接。但直接拼接 @table_name 有 SQL 注入风险,必须做白名单校验:
- 先定义允许的表名列表:
SET @allowed_tables = 'users,orders,products'; - 用
FIND_IN_SET(@input_table, @allowed_tables)判断是否合法 - 再用
CONCAT('SELECT * FROM ', @input_table, ' WHERE id = ?')拼接 - 若未通过校验,直接
SET @sql = 'SELECT ''invalid table'' AS result';避免空字符串导致语法错误
示例:
SET @table = 'users';
SET @id = 123;
SET @sql = CONCAT('SELECT id, name FROM ', @table, ' WHERE id = ?');
PREPARE stmt FROM @sql;
EXECUTE stmt USING @id;EXECUTE USING 后面的参数有哪些限制?
EXECUTE ... USING 只接受用户变量(@var),不支持字面量、局部变量或表达式:
- 错误写法:
EXECUTE stmt USING 123→ 报错ERROR 1210 (HY000) - 正确写法:
SET @id = 123; EXECUTE stmt USING @id; - 多个参数按顺序对应
?占位符位置,数量必须严格一致 - 参数类型由实际值决定,MySQL 不做强类型校验,但若类型不匹配(如字符串填入 INT 字段),可能触发隐式转换或截断
为什么 PREPARE/EXECUTE 在存储过程中容易出错?
在存储过程里反复调用 PREPARE 而不 DEALLOCATE,会导致“SQL statement not found”或内存泄漏:
- 每次
PREPARE stmt FROM ...都会注册一个新语句句柄,同名会覆盖但旧句柄仍占资源 - 建议统一用动态名称:
SET @stmt<em>name = CONCAT('stmt</em>', UNIX_TIMESTAMP()); - 或每次执行前先
DEALLOCATE PREPARE IF EXISTS stmt; - 更稳妥的做法:只在必要时动态构建,多数场景优先用条件
IF+ 静态 SQL 分支处理
动态 SQL 真正难的不是语法,而是控制权移交后的校验盲区——表名没过滤、参数没赋值、句柄没释放,任何一个环节漏掉,都会让错误延迟到运行时才暴露。

















