MySQL 8.0 存储过程变慢的主因不是本身变慢,而是默认行为更严格导致执行计划复用失败、MVCC开销增大(如REPEATABLE-READ下版本链遍历加重)、DETERMINISTIC校验失效禁用缓存、原子DDL部署耗时增加,且缓存仅限单连接、不跨会话,结合连接池无sticky session时实际无法复用。

MySQL 8.0 存储过程变慢的常见原因
不是存储过程本身“变慢”,而是 8.0 的默认行为和校验机制让旧写法更容易踩坑。同一段逻辑在 5.7 上稳定,在 8.0 上耗时波动大,往往是因为执行计划无法复用或 MVCC 开销突增。
-
REPEATABLE-READ成为默认隔离级别,SELECT ... FOR UPDATE类语句在高并发下要遍历更长的版本链,延迟明显上升 - 哪怕声明了
DETERMINISTIC,只要过程里用了NOW()、@var或RAND(),MySQL 就会在首次调用时标记为非确定性,禁用执行计划缓存 - 原子 DDL(
DROP PROCEDURE+CREATE PROCEDURE)必须同步 binlog、刷盘、更新数据字典,单次部署耗时可能比 5.7 高 3–10 倍
执行计划缓存失效比你想的更频繁
很多人以为“写进存储过程就自动缓存”,其实缓存只对满足严格条件的静态 SQL 生效,且完全不跨连接。
- 缓存前提是:SQL 文本完全一致(空格、换行、大小写都算),参数仅通过
?占位符传入;SELECT * FROM t WHERE id = 123这种带字面量的语句不缓存 - 含
CONCAT拼表名、字段名,或用PREPARE stmt FROM @sql动态拼接,每次都会触发全新解析与优化 - 缓存只存在于**同一个数据库连接内**;连接池未开启 sticky session 时,两次
CALL很可能落在不同连接上,缓存彻底失效
调试和监控几乎不可行
线上出问题时,你没法像查普通 SQL 那样拿到真实执行细节。
-
EXPLAIN PROCEDURE_NAME是无效语法;想分析某条语句,得手动拆出来加EXPLAIN,但过程内依赖@var、临时表或会话状态,直接复制会报错 -
slow_query_log只记录CALL入口,不记录内部子查询;performance_schema.events_statements_history中每条语句被聚合为一条CALL记录,锁等待、IO、耗时全丢失 - 死锁日志里只显示线程 ID,不带存储过程名;
SHOW ENGINE INNODB STATUS中的LATEST DETECTED DEADLOCK段落无法回溯到具体过程逻辑
云环境和迁移风险远超预期
本地能跑 ≠ 上线能用,尤其在主流云厂商和新数据库上。
- 阿里云 RDS、腾讯云 CDB 默认禁用存储过程(需白名单申请);华为云 GaussDB(MySQL版) 完全不支持
CALL - 迁移到 Aurora 或 TiDB 时,
CALL语句基本不可用,必须重写为应用层事务 - MySQL 5.7 到 8.0 升级中,
DECLARE CONTINUE HANDLER等语法行为变化不会在创建时报错,而是在某次调用中静默失败
真正容易被忽略的是:存储过程的“性能优势”高度依赖连接生命周期和上下文稳定性。一旦应用层使用短连接或无粘性连接池,所有预编译、变量传递、缓冲池预热都会失效——这时候它不仅没比应用层 SQL 快,反而更难定位、更难回滚、更难迁移。


















