首次执行快、再次变慢主因是prepare未释放致对象累积、临时表残留引发元数据争用、字符集隐式转换干扰索引选择,或动态sql拼接触发重复解析;需配对deallocate、显式drop临时表、统一collation并强制collate对齐。

存储过程首次执行快、再次执行变慢的典型原因
这不是缓存带来的“越跑越快”,而是执行计划被污染或参数嗅探失效导致的反常现象。MySQL本身不支持SQL Server意义上的参数嗅探,但类似问题会以另一种形式出现:PREPARE语句重编译、临时表未清理、字符集隐式转换干扰索引选择,或存储过程中动态SQL的CONCAT拼接触发了重复解析。
动态SQL拼接后未释放prepare statement
常见于用SET @sql = CONCAT(...) + PREPARE stmt FROM @sql构建查询的场景。如果每次调用都PREPARE但没DEALLOCATE PREPARE stmt,MySQL内部会累积大量未释放的预处理对象,后续执行时可能因元数据锁争用或解析开销上升而变慢。
- 必须在
EXECUTE stmt后立即加DEALLOCATE PREPARE stmt - 避免在循环内反复
PREPARE同一逻辑——提取为一次准备、多次执行 - 检查
SHOW STATUS LIKE 'Com_prepare_sql'和Com_dealloc_sql是否严重失衡
临时表残留导致后续执行走错路径
存储过程中创建CREATE TEMPORARY TABLE后未显式DROP,下次调用时虽同名表不可见(临时表会话级隔离),但若逻辑里有IF NOT EXISTS判断+建表,可能因information_schema.COLUMNS查询延迟或锁等待拖慢初始化。
- 所有
CREATE TEMPORARY TABLE后,应在退出前配对DROP TEMPORARY TABLE - 避免在
IF NOT EXISTS检查中依赖information_schema——它本身在高并发下就慢 - 大中间结果优先用带索引的临时表,但索引要在
CREATE时直接定义,别等建完再ALTER
字符集/排序规则隐式转换破坏索引
当存储过程参数声明为VARCHAR(64) CHARSET utf8mb4,但表字段是utf8mb4_0900_as_cs,而WHERE条件里又没显式COLLATE对齐,MySQL优化器可能放弃走索引,首次执行靠统计信息“蒙对”了,后续因缓存了错误计划或数据分布变化暴露问题。
- 统一数据库、表、列、连接、存储过程参数的
CHARACTER SET和COLLATION - 在关键WHERE子句中,对参数强制指定排序规则:
WHERE name = param COLLATE utf8mb4_0900_as_cs - 用
EXPLAIN FORMAT=TRADITIONAL对比首次与后续执行的key和rows字段是否一致
真正麻烦的是多种因素叠加:比如一个带PREPARE的存储过程,里面又建了临时表,还传入了未对齐字符集的字符串参数——这时候单看某一点都正常,合起来就稳稳变慢。排查时得盯住slow_query_log里每条语句的实际执行时间、扫描行数,而不是只信“存储过程执行耗时”。











