MySQL动态SQL必须用PREPARE+EXECUTE,不能直接执行拼接字符串;PREPARE仅编译校验,EXECUTE才执行;需显式DEALLOCATE或动态命名避免句柄冲突;USING传参更安全但仅支持值替换,表名列名等须拼接并校验字符集。

MySQL动态SQL必须用PREPARE + EXECUTE,没有“字符串拼接完直接执行”这回事
MySQL不允许把拼好的SQL字符串当语句直接运行,比如SET @sql = 'SELECT * FROM users WHERE id = 1'; EXECUTE @sql;会报错——EXECUTE只认预处理语句句柄,不认字符串变量。必须走PREPARE stmt_name FROM @sql这一步,把字符串编译成可执行的stmt对象。
-
PREPARE不是执行,只是语法检查+计划缓存,失败时抛错(如表不存在、字段名错) - 同一个
stmt_name重复PREPARE会覆盖前一个,不用手动DEALLOCATE也能重用 - 不能在
PREPARE里直接用存储过程参数,得先拼进@sql字符串,或用USING传参(见下一条)
用USING传参比拼接更安全,但只支持IN参数且不能传表名/列名
拼接@sql字符串时手抖漏个单引号,或者用户输入带',立刻SQL注入。用USING能把值当参数绑定,MySQL自动转义:
SET @sql = 'SELECT * FROM users WHERE status = ? AND created_at > ?'; PREPARE stmt FROM @sql; EXECUTE stmt USING @status, @min_time;
但注意:?只能替换值(WHERE右边、ORDER BY里的数字),不能换表名、列名、ORDER BY字段名、LIMIT偏移量——这些必须拼进@sql字符串,且要自己校验合法性。
- 传参类型由变量值决定,
SET @x = 123和SET @x = '123'生成的占位符行为不同 -
USING后变量名必须带@,且不能是存储过程参数名(如p_id),得先赋值给用户变量@id := p_id - 如果
USING的变量为NULL,对应位置会变成IS NULL而非= NULL,逻辑可能意外
PREPARE在存储过程中不能跨IF分支复用同名stmt,否则报Unknown prepared statement
看起来下面这段代码没问题:
IF p_type = 'user' THEN SET @sql = 'SELECT * FROM users'; ELSE SET @sql = 'SELECT * FROM orders'; END IF; PREPARE stmt FROM @sql; EXECUTE stmt;
但实际运行时,第二次调用(比如p_type从user变orders)会失败:MySQL认为stmt已存在,而PREPARE stmt FROM ...在已有同名stmt时不会隐式DEALLOCATE,反而报错。必须显式清理:
- 每次
PREPARE前加IF EXISTS(SELECT 1 FROM INFORMATION_SCHEMA.PROCESSLIST WHERE ID = CONNECTION_ID()) THEN DEALLOCATE PREPARE stmt; END IF;太重——直接写DEALLOCATE PREPARE stmt;再PREPARE更干脆 - 更稳妥的做法是用动态stmt名:
SET @stmt_name = CONCAT('stmt_', UNIX_TIMESTAMP()); PREPARE @stmt_name FROM @sql;,但EXECUTE不支持变量名,得用EXECUTE IMMEDIATE(MySQL 8.0.16+)或改用EXECUTE配合@stmt_name字符串(不推荐) - 简单场景就老老实实每段分支都
DEALLOCATE+PREPARE
字符集不一致会导致PREPARE报Illegal mix of collations
拼@sql时混用不同字符集的字符串(比如表注释是utf8mb4,但连接默认latin1),PREPARE阶段就卡住。常见于用CONCAT()拼接中文字段名或条件值时:
- 确保客户端连接字符集是
utf8mb4:SET NAMES utf8mb4;或连接时指定?charset=utf8mb4 - 所有参与拼接的变量都显式转码:
CONVERT(p_table_name USING utf8mb4) - 避免在
CONCAT()里直接写中文字符串,改用_utf8mb4'中文'前缀声明 - 查
INFORMATION_SCHEMA拿到的表名/列名默认带库字符集,用COLLATE utf8mb4_0900_as_cs强制统一
动态SQL最麻烦的从来不是语法,而是错误总在运行时才暴露,而且堆栈不指向你写的那行PREPARE,而是指向内部解析器——所以拼之前多SELECT @sql;看一眼,比啥都管用。


















