PREPARE阶段仅做语法解析、语义检查和生成模板级执行计划,不访问数据;EXECUTE时填入参数复用该计划,提升性能但可能因参数分布不均导致索引选择次优。

PREPARE 阶段只做语法解析和计划生成,不碰数据
执行 PREPARE stmt FROM 'SELECT * FROM orders WHERE user_id = ?' 时,MySQL 并不会去查 orders 表,也不会验证 user_id 列是否存在、索引是否有效。它只做三件事:词法分析(识别 ?)、语义检查(确认表和列名合法)、生成初始执行计划(比如决定是否用 user_id 索引)。这个计划是“模板级”的——优化器不知道 ? 最终是 1 还是 1000000,所以可能选错索引,尤其当列值分布极不均匀时。
- 常见错误现象:
ERROR 1295 (HY000): This command is not supported in the prepared statement protocol yet,说明你试图对CREATE TABLE或SET @var = ...这类语句用PREPARE,这是协议限制,不是 SQL 写错了 - 占位符
?只能出现在值的位置,不能当表名、列名、ORDER BY字段用:SELECT * FROM ?直接报语法错误 - MySQL 8.0+ 支持动态拼接:
SET @sql = 'SELECT * FROM users WHERE id = ?'; PREPARE stmt FROM @sql;,但@sql必须是已赋值的字符串变量,且不能嵌套占位符
EXECUTE 复用执行计划,跳过重复解析开销
EXECUTE stmt USING @user_id 才真正跑查询。此时 MySQL 把 @user_id 的值填进 ?,直接调用 PREPARE 阶段缓存好的执行计划。这意味着:100 次相同结构的查询,只做 1 次解析 + 100 次执行,省下大量 CPU 时间;尤其在高并发写日志、批量导入等场景,性能提升明显。
- 参数必须用用户变量(
@var),不能直接写值:EXECUTE stmt USING 123会报错 - 变量类型影响行为:如果
SET @id = '123'(字符串),而user_id是INT,MySQL 会隐式转换,可能导致索引失效 - 执行计划不会因真实参数值重新优化——哪怕第一次传的是 1(命中索引),第二次传的是 999999(实际该走全表扫描),MySQL 仍硬跑老计划
每个连接独立维护,断连自动清理但显式释放更稳妥
预处理语句绑定在当前连接上,其他连接看不见也用不了。连接断开时,MySQL 会自动回收 stmt 资源,所以不写 DEALLOCATE PREPARE stmt 也不会泄漏内存。但长连接中若反复 PREPARE 却不 DEALLOCATE,可能耗尽服务器端语句句柄(默认 max_prepared_stmt_count = 16382)。
- 长时间空闲的预处理语句可能被服务器自动清理,再次
EXECUTE会报Unknown prepared statement handler - 建议在业务逻辑明确结束时主动
DEALLOCATE PREPARE stmt,避免依赖自动回收的不确定性 - PHP/PDO 等客户端库通常在
$stmt->close()或对象销毁时自动释放,但纯 SQL 交互必须自己管
安全与性能本质来自“结构和数据分离”
预处理语句防注入不是靠转义,而是因为 SQL 结构(SELECT * FROM users WHERE id = ?)在 PREPARE 阶段就固定了,后续 EXECUTE 只是往既定位置塞值,攻击者没法插入额外的 OR 1=1 或闭合引号。性能优势也不是玄学——它把原本每次都要走的“解析 → 优化 → 计划生成”流水线,压缩成一次前期投入、多次零成本复用。
最易被忽略的一点:执行计划缓存是“单语句级”的。如果你有 WHERE status = ? AND created_at > ?,但两个 ? 经常传入高度相关的值(比如 status='active' 总配近七天时间),优化器在 PREPARE 阶段无法感知这种关联性,生成的计划可能长期次优。这时候就得考虑用 OPTIMIZE TABLE 或定期 ANALYZE TABLE 更新统计信息,而不是只依赖预处理本身。


















