mysql 5.7 存储过程首次执行慢主因是内部sql语句需完整解析、权限检查及生成执行计划,耗时集中于init和checking permissions阶段;后续调用可复用计划而变快,但受sql文本一致性、禁用用户变量、避免now()/rand()等非确定性函数、统计信息更新等因素影响缓存有效性。

第一次执行慢是因为执行计划缓存未命中
MySQL 5.7 存储过程本身不编译,但其中每条 SQL 语句在首次执行时会经历完整解析、权限检查、优化器生成执行计划、缓存该计划等步骤。这个过程在 SHOW PROFILE 中表现为耗时集中在 init 和 checking permissions 阶段——不是 SQL 慢,是“准备动作”重。
后续调用能复用上一次生成的执行计划(只要满足复用条件),跳过解析与优化,直接进入执行阶段,所以明显变快。
- 复用前提是:SQL 文本完全一致(空格、换行、大小写都不能差)
- 不能含
@var用户变量,也不能引用临时表 - 表结构、索引或统计信息未变更;否则缓存自动失效
- 若用了
NOW()、RAND()等非确定性函数,即使加了DETERMINISTIC声明,5.7 也不会校验运行时行为,仍可能复用(但这是隐患,8.0 会直接禁用)
为什么有些存储过程后续也慢?常见破环缓存的操作
你以为缓存稳了,其实随时可能被悄悄清掉。最典型的是在过程里偷偷用了用户变量或时间函数,导致每次调用都被当成新语句处理。
-
SET @a := @a + 1;—— 即使只赋值没读,5.7 也会认为该语句不可复用 -
WHERE created_at > NOW() - INTERVAL 1 DAY——NOW()是非确定性函数,每次值不同,缓存失效 - 过程体中动态拼接 SQL 并用
PREPARE/EXECUTE执行,每次生成的语句文本不同,必然硬解析 - 升级后未执行
ANALYZE TABLE,统计信息陈旧,优化器判断“旧计划不再适用”,主动丢弃缓存
如何验证执行计划是否真被复用?
别猜,用命令看。重点不是“有没有缓存”,而是“这次用没用上”。
- 执行
SELECT @@session.query_cache_type;确认 query cache 已关闭(5.7 默认关,且它对存储过程无效,别被误导) - 在存储过程调用前后,查
SELECT * FROM performance_schema.events_statements_summary_by_digest WHERE DIGEST_TEXT LIKE '%your_sql%';,对比EXECUTIONS和AVG_TIMER_WAIT - 更直接:开两个会话,一个持续调用存储过程,另一个实时查
SHOW PROCESSLIST,如果反复卡在init或checking permissions,说明缓存根本没生效
真正影响性能的往往是隐式锁和事务行为
第一次慢 ≠ 后续就一定快。很多场景下,后续执行反而更卡,原因不在缓存,而在事务和锁机制被激活。
- 存储过程中含
START TRANSACTION但没指定隔离级别 → 继承会话默认值REPEATABLE-READ,高并发下SELECT FOR UPDATE遍历版本链变长 - 过程内更新多行且未及时
COMMIT→ 锁持有时间拉长,阻塞其他会话,造成“越调用越慢”的假象 - 用
IN传入几百个 ID,MySQL 解析超长字面量列表,每次都要重建语法树,无法靠缓存挽救
第一次执行慢是常态,但后续不稳定才是真问题。关键不是等它“热起来”,而是确认哪些操作在暗中破坏缓存,以及事务和锁是否在放大延迟——这两点比“为什么第一次慢”更值得花时间盯住。











