prepare 解析并缓存参数化sql的执行计划但不执行,execute 填充参数并复用该计划;二者是连接级资源,仅适合多次执行同一模板的场景。

prepare 语句到底做了什么
PREPARE 不是执行 SQL,而是让 mysql 解析、校验并缓存一条参数化 SQL 的执行计划。它把 select * FROM users WHERE id = ? 这类带问号占位符的语句编译成内部结构,绑定到一个语句名(比如 stmt1),但此时不查表、不读数据、也不检查 ? 对应的值是否存在。
常见错误现象:Error 1295 (HY000): this command is not supported in the prepared statement protocol yet —— 某些语句(如 CREATE table、SET @var = ...)在旧版 MySQL 中不支持预处理,不是语法写错了,是协议限制。
- 只支持单条语句,不能含多个分号(
;)或注释块(/* */) - 占位符
?不能出现在标识符位置(比如不能写SELECT * FROM ?或ORDER BY ?) - MySQL 8.0+ 支持
PREPARE ... FROM @sql动态拼接,但@sql必须是字符串变量,且内容需提前用SET @sql = 'SELECT ...'赋值
execute 传参时类型和顺序怎么对上
EXECUTE 才真正触发执行,它把具体值按顺序填入 ? 占位符,并复用 PREPARE 阶段生成的执行计划。值的类型由 MySQL 根据字段定义隐式推断,**不依赖你传的是字符串还是数字**——但顺序和个数必须严格匹配 PREPARE 语句里的 ? 数量。
典型翻车点:EXECUTE stmt1 using @a, @b, @c 中,如果 PREPARE stmt1 只有 2 个 ?,就会报错 ERROR 1210 (HY000): Incorrect arguments to EXECUTE。
- 传参只能用用户变量(
@var),不能直接写字面量(EXECUTE stmt1 USING 123是非法的) - 变量类型影响结果:若字段是
int,但传了@x = '123abc',MySQL 会截断转成123;若传@x = 'abc',则转成0,无警告 - NULL 值要显式用
SET @x = NULL,不能留空或传空字符串
prepare/execute 在连接生命周期里怎么管理
预处理语句是**连接级资源**,每个客户端连接独立维护自己的 stmt 名称空间。断开连接后,所有 PREPARE 的语句自动释放,不需要手动 DEALLOCATE PREPARE —— 但长连接中不清理会占用内存,尤其在存储过程中反复 PREPARE 同一名字(如 stmt1)会覆盖前一个,旧计划被丢弃。
容易忽略的细节:在存储过程中使用 PREPARE,如果过程被多次调用,每次 PREPARE stmt1 实际上是重定义,不会报错,但可能掩盖本意(比如想复用旧计划却意外重建)。
- 显式释放用
DEALLOCATE PREPARE stmt1,释放后同名再PREPARE算新语句 - 同一连接中,
PREPARE stmt1后又PREPARE stmt1,前者立即失效,不报错 - 事务中
PREPARE不受事务控制,ROLLBACK不会影响已PREPARE的语句
为什么有时候 prepare + execute 比直接执行还慢
预处理不是银弹。它的优势在于**重复执行同一模板、仅参数变化**的场景(比如批量插入、分页查询)。但如果只执行一次,或 SQL 很简单(如 SELECT 1),PREPARE 的解析、缓存、后续 EXECUTE 的调度开销反而更高。
更隐蔽的问题:MySQL 的预处理计划不会随底层表结构变更自动更新。例如给表加了索引,已 PREPARE 的语句仍沿用旧执行计划,直到 DEALLOCATE 后重新 PREPARE 或连接断开。
- 可通过
SHOW WARNINGS查看EXECUTE是否触发了隐式类型转换(影响索引使用) -
EXPLAIN EXECUTE stmt1 USING @a在 MySQL 8.0.22+ 才支持,老版本只能EXPLAIN原始 SQL 字符串 - 开启
general_log可看到 prepare/execute 分开记录,确认是否真复用了计划
预处理真正的复杂点不在语法,而在于它把“解析”和“执行”拆成两步,这两步之间隔开了连接状态、变量作用域、类型推导时机和执行计划缓存策略——稍不注意,你以为在复用计划,其实已在重建;你以为传了 NULL,实际塞进去的是空字符串。