MySQL存储过程CPU飙升主因是游标逐行处理、参数嗅探致执行计划失效、自定义函数未走索引三者叠加,应改用集合操作、加优化器提示、确保函数内查询命中索引。

存储过程里用了游标,CPU直接拉满
游标在 MySQL 中是逐行处理数据的机制,每次 FETCH 都触发一次引擎层调用、一次锁检查、一次内存分配。当处理几万行数据时,相当于执行几万次独立的 UPDATE/INSERT,CPU 花费全砸在解析、计划重用、上下文切换上,而不是真正做计算。
常见错误写法:DECLARE cur CURSOR FOR SELECT ... + FETCH NEXT FROM cur INTO ... + 循环体里再嵌套 UPDATE 或 INSERT。
- 替代方案永远优先用集合操作:比如把游标里的单行
UPDATE改成带JOIN的批量UPDATE - 如果必须用逻辑分支,考虑用临时表 + 多个
UPDATE ... WHERE分段代替循环 - MySQL 8.0+ 可用
CTE+RECURSIVE替代简单递归逻辑,但注意深度限制和优化器支持度
参数嗅探失效,导致执行计划反复编译
MySQL 虽然不像 SQL Server 那样有显式的“参数嗅探”机制,但它对存储过程中首次传入的参数值敏感——SELECT 语句若含参数条件,优化器会基于该次参数估算行数,生成执行计划并缓存。后续参数差异大(比如一个查 10 行,一个查 10 万行),旧计划就可能严重低效,引发大量回表、临时表、文件排序,CPU 持续飙高。
- 现象:同一个存储过程,不同参数下执行时间差异极大,
SHOW PROFILES显示executing或Sorting result占比异常高 - 验证方式:在存储过程开头加
SELECT @@session.optimizer_search_depth;,确认是否被动态调整过 - 缓解办法:在关键查询前加
/*+ NO_ICP() */或/*+ USE_INDEX(...) */提示,或拆成预编译语句(PREPARE/EXECUTE)手动控制计划生成时机
没设 DETERMINISTIC 或权限不足,触发隐式重编译
MySQL 存储过程默认是 NOT DETERMINISTIC,且若调用者没有 EXECUTE 权限以外的额外权限(如对内部查询表的 SELECT 权限),服务端每次执行都可能跳过计划缓存,走完整解析路径——尤其在连接池复用场景下,看似“同过程多次调用”,实则每次都在重新 parse、optimize、generate plan。
- 检查方式:执行
SELECT db, name, type, definer, security_type FROM mysql.proc WHERE name = 'your_proc_name';,看security_type是DEFINER还是INVOKER - 修复动作:创建时显式声明
DETERMINISTIC(仅当确实不依赖外部状态),并确保DEFINER用户拥有过程内所有对象的必要权限 - 副作用注意:
DETERMINISTIC声明后若实际逻辑非确定(比如含NOW()、RAND()),会导致主从不一致或复制中断
存储过程里调了函数,而函数没走索引
最容易被忽略的一点:存储过程里调用的自定义函数(CREATE FUNCTION),如果函数体里有 SELECT ... WHERE col = xxx 且 col 没索引,这个查询会在每次函数调用时全表扫描——而函数又常被放在 SELECT 列表或 WHERE 子句中,导致 N × M 级放大(N 行结果 × M 次函数调用)。
- 典型陷阱:
SELECT id, get_user_status(user_id) FROM orders,而get_user_status内部查users表但没走user_id索引 - 快速定位:开启
performance_schema,查events_statements_history_long,过滤出object_name为函数名的记录,看rows_examined是否异常高 - 解法不是禁用函数,而是确保函数内所有查询都命中索引;更彻底的是把函数逻辑内联进存储过程主体,避免重复调用开销
真实线上问题里,CPU 飙升往往不是单点原因,而是游标 + 参数失配 + 函数无索引三者叠加。别急着 kill 进程,先抓一条正在跑的慢过程 ID,用 SHOW FULL PROCESSLIST 看它卡在哪一步,再结合 EXPLAIN FORMAT=TREE 对应语句——很多“高 CPU”本质是高 I/O 等待被误判,%wa 高却只盯着 %us 就容易走偏。











