存储过程不能直接用explain分析,需单独提取内部sql语句加explain执行;应优先用show profile定位耗时阶段;避免循环逻辑,改用批量操作;注意隐式转换、锁竞争和事务设计等系统层问题。

存储过程里不能直接用 EXPLAIN
MySQL 存储过程本身没有执行计划——EXPLAIN 只能作用于 SELECT、UPDATE、DELETE、INSERT 这类 DML 语句,而不能对 CALL 语句生效。你执行 EXPLAIN CALL proc_name() 会报错或返回无意义结果。
真正要分析的,是存储过程中实际执行的 SQL 语句。必须把它们单独拎出来,加 EXPLAIN 再看。
- 打开存储过程定义:
SHOW CREATE PROCEDURE proc_name,复制其中的每条核心 SQL(尤其是带WHERE、JOIN、ORDER BY的) - 把
SELECT ...或UPDATE ...单独拿出来,在前面加EXPLAIN执行 - 注意替换变量:比如原过程里是
WHERE user_id = p_user_id,测试时得换成具体值如WHERE user_id = 123 - 避免动态拼接 SQL:如果过程里用了
CONCAT+PREPARE,EXPLAIN无法静态分析,得先还原出最终语句再分析
用 SHOW PROFILE 定位耗时环节
SHOW PROFILE 是唯一能反映存储过程内部执行阶段耗时的原生工具,它会把一次 CALL 拆成多个阶段(如 executing、Copying to tmp table、Sorting result),帮你确认瓶颈在 SQL 执行、临时表、排序还是锁等待。
- 启用前先开 profiling:
SET profiling = 1 - 执行存储过程:
CALL my_proc(100) - 查耗时明细:
SHOW PROFILE FOR QUERY 1(数字对应最近一次查询序号) - 重点关注
Duration列最大那几行,比如Creating sort index耗时高,说明ORDER BY没走索引;Copying to tmp table频繁出现,大概率有GROUP BY或DISTINCT缺少覆盖索引 - 注意:该功能在 MySQL 8.0 中已被标记为 deprecated,生产环境慎用,仅用于临时诊断
循环逻辑是存储过程最常见性能杀手
存储过程里写 WHILE 或游标遍历,几乎必然比等效的集合操作慢一个数量级。不是执行计划的问题,而是架构层面的设计缺陷。
- 典型坏模式:
FETCH一行 →UPDATE一行 →FETCH下一行 - 正确做法:把循环体内的单行 SQL 改成批量操作。例如把
UPDATE t SET status=1 WHERE id = @id替换为UPDATE t SET status=1 WHERE id IN (SELECT id FROM temp_batch) - 如果必须逐行处理(比如依赖上一行计算结果),至少用
START TRANSACTION+ 批量提交(如每 100 行COMMIT),避免事务日志刷盘过频 - 游标默认是
NO SCROLL且不支持ORDER BY优化,若需排序,先建临时表并加索引,再从临时表游标读取
别忽略系统层和上下文影响
存储过程执行慢,有时和 SQL 本身无关,而是被外部因素拖累。这些不会体现在 EXPLAIN 里,但 SHOW PROCESSLIST 和慢日志能暴露问题。
- 检查是否被锁住:
SHOW PROCESSLIST看状态是不是Locked或Waiting for table metadata lock - 确认是否触发了隐式转换:比如过程参数声明为
INT,但传入字符串,导致索引失效——这时EXPLAIN显示key为NULL - 慢日志里记录的是整个
CALL耗时,但看不出哪条 SQL 慢。开启log_queries_not_using_indexes = ON,配合pt-query-digest分析,能筛出过程内未走索引的语句 - 存储过程内多次调用同一条查询,又没缓存结果,反复解析+执行。可考虑用临时表存中间结果,或改用函数封装复用逻辑
EXPLAIN 的框,把过程拆成 SQL、控制流、系统状态三层来看。











