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

PREPARE 不执行 SQL,只做语法解析和执行计划缓存
PREPARE 的本质不是运行查询,而是让 MySQL 把带 ? 占位符的语句(比如 SELECT * FROM users WHERE id = ?)做一次词法/语义分析、优化器决策,并把生成的执行计划绑定到一个语句名(如 stmt1)上。这个过程不访问表、不读数据、也不校验 ? 对应的值是否存在。
常见错误现象:ERROR 1295 (HY000): This command is not supported in the prepared statement protocol yet——这不是你写错了 SQL,而是 MySQL 协议层面限制:旧版本不支持对 CREATE TABLE、SET @var = ... 等语句做预处理。
- 只能是单条语句,不能含分号(
;)或块注释(/* */) -
?只能出现在值的位置,不能当表名、列名、ORDER BY 字段等标识符用(SELECT * FROM ?是非法的) - MySQL 8.0+ 支持
PREPARE stmt FROM @sql动态拼接,但@sql必须是已赋值的字符串变量,且内容不能含嵌套占位符
EXECUTE 才真正跑查询,复用 PREPARE 阶段的执行计划
EXECUTE 触发实际执行:它把 USING 后面的变量(如 @user_id)按顺序填进 ?,然后直接调用 PREPARE 阶段缓存好的执行计划。这意味着:相同结构的 SQL 多次执行时,跳过了重复解析和优化开销。
但要注意——执行计划是“模板级”的。比如 WHERE id = ? 在 PREPARE 阶段无法知道具体值,优化器可能选错索引;真实值传进来后,MySQL 不会重新优化,只会硬跑那个计划。
- 参数必须用用户变量(
@var),不能直接写字面量(EXECUTE stmt USING 123是错的) - 变量类型影响执行行为:整数传成字符串变量(
SET @id = '123')可能触发隐式转换,导致索引失效 - 每个连接独立维护自己的预处理语句,断连后自动清理,不用手动
DEALLOCATE也能回收,但显式释放更稳妥
为什么有些场景下预处理反而变慢?
预处理不是银弹。当查询结构简单、执行频次低,或者参数值差异极大(比如一个查主键、一个查全表扫描条件),复用固定执行计划反而不如每次重优化来得快。
典型反例:用同一个 PREPARE 语句查 WHERE status IN (?),传入 @s = 'active' 和 @s = 'archived,deleted,pending',后者因字符串拼接导致无法走索引,而执行计划早已固化。
- 高频小查询(如每秒上百次
SELECT id FROM t WHERE pk = ?)收益明显 - 涉及范围查询、IN 列表、LIKE 前缀模糊匹配的,要小心执行计划僵化问题
- JDBC 连接串里必须加
useServerPrepStmts=true,否则PreparedStatement只是客户端模拟,没走服务端预编译
DEALLOCATE PREPARE 不是必须的,但漏掉可能有隐患
MySQL 会为每个连接维护预处理语句列表,上限由 max_prepared_stmt_count 控制(默认 16382)。不释放的话,长期运行的连接可能耗尽这个配额,后续 PREPARE 直接报错 ERROR 1461 (HY000): Can't create more than max_prepared_stmt_count statements。
另外,如果同一连接反复定义同名语句(PREPARE stmt FROM ...),新定义会覆盖旧的,但旧资源未必立即释放——尤其在高并发短连接场景下,容易堆积。
- 建议在业务逻辑结束或异常分支里加
DEALLOCATE PREPARE stmt - 存储过程中定义的预处理语句,退出过程时自动释放,无需手动处理
- 临时调试时可查
SHOW GLOBAL STATUS LIKE '%Com_stmt%',看Com_stmt_prepare和Com_stmt_deallocate是否大致平衡
实际用的时候,别只盯着“防注入”和“性能好”这两个标签。PREPARE/EXECUTE 是把双刃剑——它省的是解析开销,换来的可能是执行计划不够聪明。真正关键的,是判断你的查询是否“结构稳定 + 参数值分布均匀 + 执行频次足够高”。











