MySQL存储过程本身不慢,真正变慢的是其内部某条SQL执行计划退化或隐性开销:统计信息过期致索引失效、临时表落盘引发I/O飙升、变量传参污染执行计划缓存、游标逐行执行、字符集/类型不匹配触发隐式转换等。

MySQL 存储过程本身不慢,真正变慢的是它内部某条 SQL 的执行计划退化、资源争用或隐式开销——比如统计信息过期导致索引失效、临时表落盘、变量传参污染执行计划缓存、游标逐行执行、字符集不匹配触发隐式转换等。
EXPLAIN 不生效?别对 CALL 做 EXPLAIN,要对过程内具体 SQL 做
很多人直接 EXPLAIN CALL proc_name(),结果返回空或无意义信息。MySQL 的 EXPLAIN 只作用于单条可执行语句(SELECT/UPDATE/INSERT),不解析存储过程体。
- 把过程里疑似慢的语句复制出来,手动代入真实参数值再
EXPLAIN,例如:EXPLAIN SELECT * FROM orders WHERE user_id = 12345 AND status = 'shipped'; - 绝对不要用
WHERE id = in_id这类带参数变量的写法去EXPLAIN,优化器无法估算选择性,rows值严重失真 - 如果必须验证变量行为,可用
PREPARE stmt FROM 'SELECT ... WHERE id = ?'; EXECUTE stmt USING @val;搭配EXPLAIN FORMAT=TRADITIONAL观察真实计划
临时表从内存掉到磁盘?看 Created_tmp_disk_tables
存储过程里一旦出现 GROUP BY、ORDER BY(无索引字段)、CREATE TEMPORARY TABLE 或隐式临时表,就可能触发磁盘临时表——这是 I/O 爆增的典型信号。
- 运行
SHOW STATUS LIKE 'Created_tmp%';,重点盯Created_tmp_disk_tables是否持续上升 - 临时缓解:会话级调大
tmp_table_size和max_heap_table_size,如SET SESSION tmp_table_size = 268435456; - 长期解法:给
ORDER BY/GROUP BY字段加覆盖索引,或改写逻辑避免排序;临时表务必加主键/唯一索引,否则后续JOIN会退化为嵌套循环
游标遍历万级数据?CPU 飙高、锁堆积、根本不是“写法问题”
游标慢不是因为没加索引,而是执行模型缺陷:每次 FETCH 都走完整 SQL 生命周期(解析→优化→执行→构造单行结果),完全绕过存储引擎批量通道。
- 典型症状:
SHOW PROCESSLIST中大量Sleep或Locked状态;performance_schema.events_statements_history_long里同名游标反复出现不同PLAN_EXPLAIN - 正确替代路径:先
CREATE TEMPORARY TABLE tmp_ids (id BIGINT PRIMARY KEY);,再INSERT INTO tmp_ids SELECT id FROM ...,最后UPDATE t1 JOIN t2 JOIN tmp_ids ... - 关键点:临时表必须有主键或唯一索引,否则
JOIN效率比游标还差
加了索引还是全表扫描?查字符集和参数类型是否“表面一致、底层冲突”
常见陷阱是字段定义为 VARCHAR(36) CHARACTER SET utf8 COLLATE utf8_unicode_ci,但过程里传参写成 _utf8mb4'xxx' COLLATE utf8mb4_unicode_ci——MySQL 认为 collation 不兼容,强制放弃索引做逐行比较。
- 检查方式:
SHOW CREATE TABLE your_table;对比字段 collation 与过程内字符串字面量的 collation - 安全写法:省略
COLLATE,让 MySQL 自动按列规则推导,如WHERE ref_id = _utf8mb4'c37e32fc...' - 参数类型也要严格对齐:
IN p_id BIGINT对应BIGINT字段;若定义为VARCHAR却传数字,会触发隐式转换,索引立即失效
最常被忽略的点是:存储过程里一条未加索引的 UPDATE ... WHERE status = 'pending' 在循环中反复执行,会累积间隙锁,阻塞其他事务;而这个问题在 EXPLAIN 里根本看不到,只能靠 SHOW ENGINE INNODB STATUS\G 查 TRANSACTION 段里的 lock wait。


















