mysql 8.0 存储过程不新增缓存机制,仅复用语句级执行计划缓存,但复用条件更严苛:要求sql文本完全一致、无非确定性函数、无用户变量、表结构与统计信息未变,且deterministic声明须严格满足;否则每次调用均重新解析与校验。

MySQL 8.0 存储过程内部 SQL 的执行计划缓存更严苛,不是“默认更好”
MySQL 8.0 并没有给存储过程加一层新的缓存机制,它复用的是 prepared statement 级别的执行计划缓存(不是已移除的 query cache)。但触发复用的条件比 5.7 更多、更硬性。一旦不满足,每次调用都会重新解析、校验、生成执行计划,SHOW PROFILE 里能看到反复出现 init 和 checking permissions 阶段——这说明缓存根本没生效。
常见错误现象包括:同一存储过程在 5.7 下多次执行耗时稳定,在 8.0 下波动剧烈;明明没改表结构,却频繁重编译。
- SQL 文本必须完全一致:空格、换行、大小写都不能差
- 不能含
@var用户变量(哪怕只是赋值后没用) - 不能调用
NOW()、RAND()、UUID()等非确定性函数 - 过程定义中写了
DETERMINISTIC,但运行时触发了隐式非确定行为(比如SET @a := @a + 1),MySQL 会在首次执行后将其标记为NOT DETERMINISTIC,后续禁用缓存 - 表结构、索引、统计信息任一变更,缓存立即失效
为什么显式设 READ COMMITTED 能让存储过程变快?
这不是缓存问题,而是事务隔离级别引发的 MVCC 开销差异。MySQL 8.0 默认 transaction_isolation = 'REPEATABLE-READ',而很多 5.7 生产环境习惯设为 READ-COMMITTED。如果存储过程中有 START TRANSACTION 却没指定级别,就会继承会话默认值。
在高并发更新场景下,REPEATABLE-READ 会导致 SELECT FOR UPDATE 等语句遍历更长的版本链,延迟明显上升。这不是执行计划缓存失效,而是事务引擎本身的代价被放大了。
- 用
SELECT @@transaction_isolation;查当前实际值,别只看配置文件 - 若逻辑允许,优先在存储过程开头加
SET TRANSACTION ISOLATION LEVEL READ COMMITTED; - 若必须用可重复读,考虑用
SELECT ... LOCK IN SHARE MODE替代全表扫描式FOR UPDATE
DETERMINISTIC 在 8.0 中反而容易导致缓存失效
8.0 强化了对 DETERMINISTIC 的运行时校验:你写了这个关键字,不代表 MySQL 就信你。只要过程体里出现任何非确定性操作(哪怕只是调了一次 NOW() 或用了 @var),首次执行时就会被标记为 NOT DETERMINISTIC,并直接禁用该语句的执行计划复用。
结果就是:5.7 下能稳定复用的存储过程,在 8.0 下每次都要重新 init —— 不是变慢,是“开销显性化”了。
- 用
SHOW CREATE PROCEDURE proc_name;确认定义中是否真写了DETERMINISTIC - 时间类逻辑一律改用输入参数传入,别在过程里调
NOW() - 随机值提前生成好,作为
IN参数传入,而非在过程内调RAND() - 彻底弃用
@var传参,改用标准的IN/OUT参数
字符集和排序规则变更间接影响执行计划稳定性
MySQL 8.0 默认字符集是 utf8mb4,排序规则是 utf8mb4_0900_ai_ci。这个组合在某些 JOIN 或 WHERE 条件中可能触发隐式转换,导致索引失效,进而让优化器每次都选错执行路径——看起来像缓存没起作用,其实是执行计划本身变了。
尤其当从 5.7 迁移过来,没显式指定建表字符集和排序规则时,dump/restore 后的表可能用上了新规则,但旧索引未重建,查询行为就悄悄偏移了。
- 建表时务必显式声明
CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci(避免依赖utf8mb4_0900_ai_ci的细微差异) - 迁移后跑一次
ANALYZE TABLE,确保统计信息与新规则匹配 - 对关键查询用
EXPLAIN FORMAT=TRADITIONAL对比 5.7 和 8.0 的执行计划差异











