存储过程本身不会随时间推移变慢,真正变慢的是其内部sql执行计划退化或运行环境隐性变化:统计信息过期导致索引失效、临时表因内存不足落盘引发i/o飙升、变量传参污染执行计划缓存、长事务累积锁等待等。

存储过程本身不会随时间推移变慢,真正变慢的是它内部某条 SQL 的执行计划退化或运行环境发生了隐性变化——比如统计信息过期、临时表撑爆内存、连接会话累积锁等待,或者 JSON 字段反复解析触发了磁盘临时表。
统计信息未更新导致索引失效
MySQL 优化器依赖 information_schema.STATISTICS 和 ANALYZE TABLE 生成的统计信息做执行计划选择。当表数据量增长 20% 以上或大量 INSERT/DELETE 后,统计信息可能严重失真:
-
EXPLAIN显示type=ALL或rows预估远高于实际匹配数,但索引明明存在 - 同一条语句在刚部署时走
ref,两周后变成range甚至ALL - 执行
ANALYZE TABLE orders;后立刻恢复快查,说明是统计偏差而非 SQL 本身问题
临时表从内存溢出到磁盘
存储过程里用 CREATE TEMPORARY TABLE 或隐式生成临时表(如 GROUP BY、ORDER BY 无索引字段)时,MySQL 先尝试用内存引擎(MEMORY),但受 tmp_table_size 和 max_heap_table_size 限制:
- 过程第一次运行快,后续并发调用增多,内存不足 → 临时表落盘 → I/O 暴涨
-
SHOW STATUS LIKE 'Created_tmp%';中Created_tmp_disk_tables持续上升是明确信号 - 临时解法:
SET SESSION tmp_table_size = 268435456;(256MB),长期需重构逻辑或加覆盖索引避免排序
变量传参引发执行计划缓存污染
存储过程常用 WHERE col = @user_id 这类写法,MySQL 5.7+ 会为每个不同值缓存一份执行计划,但优化器无法基于变量值准确估算选择性:
- 第一次传
@user_id = 1(高频用户),生成走索引的计划;第二次传@user_id = 999999(冷门用户),却复用旧计划 → 实际全扫 -
SHOW PROFILES;可见同一存储过程多次调用,耗时波动极大 - 改用
PREPARE stmt FROM ...; EXECUTE stmt USING @user_id;,让每次编译都基于真实值生成计划
长时间运行积累的锁与事务状态
存储过程若含 START TRANSACTION 且未及时 COMMIT,或在循环中频繁更新同一张 InnoDB 表,会持续持有行锁:
-
SELECT * FROM information_schema.INNODB_TRX ORDER BY trx_started DESC LIMIT 5;查看长事务 -
SHOW ENGINE INNODB STATUS\G中---TRANSACTION段出现大量lock wait或LOCK WAIT - 尤其注意过程里
UPDATE带范围条件(如WHERE status = 'pending')却没索引,会升级为间隙锁阻塞其他会话
最易被忽略的是:这些退化往往不报错、不告警,只表现为“今天比昨天慢一点”,直到某次数据量突增或并发翻倍才彻底卡死。定位必须打点测每条 SQL 耗时,而不是只看 CALL proc_name() 总时间——那个数字掩盖了所有细节。











