MySQL 8.0 移除 query_cache,但存储过程内静态SQL可复用执行计划缓存,需满足文本完全一致、仅用?占位符等条件;动态SQL、分支语句、调试与监控均存在显著限制。

存储过程执行计划缓存不等于查询缓存
MySQL 8.0 已彻底移除 query_cache,但存储过程内部的 SQL 仍可受益于执行计划缓存(Execution Plan Cache),前提是语句满足严格复用条件。这个缓存只对预处理语句(PREPARE + EXECUTE)和存储过程中**静态拼接、无动态 SQL 的 SELECT/UPDATE 等语句**生效。
常见误解是“只要写进存储过程,SQL 就自动缓存”。实际不是:如果存储过程里用了 CONCAT 拼接表名、字段名,或 SET @sql = ...; PREPARE stmt FROM @sql;,那每次执行都会触发全新解析与优化,执行计划无法复用。
- 执行计划缓存命中要求:SQL 文本完全一致(含空格、换行、大小写),参数仅通过
?占位符传入 -
SELECT * FROM t WHERE id = ?可缓存;SELECT * FROM t WHERE id = 123不缓存(字面量内联,非预处理) - 存储过程若含
IF ... THEN SELECT ... ELSE SELECT ...,两个分支的语句各自独立缓存,但分支逻辑本身不缓存
EXPLAIN 无法直接查看存储过程内语句的执行计划
对存储过程执行 EXPLAIN PROCEDURE_NAME 是无效的——MySQL 不支持该语法。想分析其中某条 SQL 的执行计划,必须把那条语句单独拎出来,手动加上 EXPLAIN 前缀运行。
更麻烦的是:存储过程里的语句可能依赖过程变量、临时表或会话状态(如 @var、CREATE TEMPORARY TABLE),直接复制到外面执行会报错或结果不同。
- 正确做法:在存储过程里加
SELECT 'debug', @var, @@session.sql_mode;输出上下文,再构造等效语句 - 临时表需改写为普通表或 CTE(MySQL 8.0+ 支持
WITH)才能用EXPLAIN分析 - 涉及游标或循环的语句,要拆解成单次迭代的逻辑再分析,否则
rows和type字段严重失真
维护成本集中在调试、版本控制与权限收敛
存储过程不像应用层 SQL 那样能被 Git 跟踪、Code Review 或 A/B 测试。一次 ALTER PROCEDURE 就全量覆盖,出问题难回滚,且无法做灰度发布。
典型痛点包括:
- 调试困难:没有断点、变量监视,只能靠
SELECT打印中间值,而SELECT在某些客户端(如 MySQL Workbench)中不显示结果集 - 权限分散:DBA 给开发
EXECUTE权限即可调用,但过程内部可能执行DROP TABLE或跨库操作,权限粒度失控 - 测试隔离差:过程常隐式依赖当前数据库、字符集、SQL mode,本地开发环境与生产稍有差异就行为不一致
- 监控盲区:慢日志只记录
CALL proc_name()这一行,看不到内部哪条 SQL 慢,除非开启log_slow_verbosity=FULL并配合 performance_schema
真正影响性能的往往是执行计划不可控
存储过程容易掩盖索引失效问题。例如一个过程里写 WHERE status IN (SELECT code FROM config WHERE type = 'active'),外表数据量小,优化器可能选错驱动表;而同样逻辑放在应用层,开发者更倾向先查出 IN 列表再拼成 WHERE status IN ('A','B','C'),反而更容易走索引。
关键差异在于:存储过程内联子查询的统计信息不可控,优化器无法预估 config 表返回多少行,成本估算偏差大;而应用层生成的字面量列表,优化器能准确判断选择性,更大概率选对索引。
另一个隐蔽点:CREATE PROCEDURE 时的 sql_mode 会被固化进过程元数据,后续即使全局改了 sql_mode,该过程仍按旧模式执行——比如旧模式允许隐式类型转换,新环境已禁用,过程却仍在悄悄转换,导致索引失效却不报警。











