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,实际塞进去的是空字符串。
