答案是sp_head::main_mem_root内存未释放所致;需查performance_schema中memory/sql/sp_head::main_mem_root用量超1GB即确认,且必须显式CLOSE游标、加异常处理、避免大结果集FETCH。
MySQL 存储过程里游标不显式 CLOSE,哪怕过程执行完,sp_head::main_mem_root 分配的内存也不会释放。多个并发调用后 RSS 内存线性上涨,轻则变慢,重则被 OOM Killer 杀掉——SHOW STATUS 里 Innodb_buffer_pool_reads 可能很低,但 performance_schema.memory_summary_global_by_event_name 里 memory/sql/sp_head::main_mem_root 却飙到几 GB。
OPEN,也必须显式 CLOSE;仅靠存储过程结束自动清理不可靠SQLEXCEPTION)下 CLOSE 容易被跳过,必须用 DECLARE CONTINUE HANDLER FOR SQLEXCEPTION 包一层CLOSE 放在 LEAVE 之后、循环外,是常见错误;正确位置应在所有退出路径前,包括正常结束和异常分支最稳妥的方式是在存储过程开头定义一个标签(如 proc_exit),再把 CLOSE 和 LEAVE 统一收口到该标签下。避免在循环体里零散写 CLOSE,也别依赖“最后写一句 CLOSE”这种看似简洁实则危险的做法。
DECLARE CONTINUE HANDLER FOR SQLEXCEPTION BEGIN CLOSE cursor_name; LEAVE proc_exit; END;
proc_exit 标签,而不是直接写 END
FETCH 和业务逻辑,IF done THEN LEAVE read_loop;,不放 CLOSE
proc_exit: 标签下只放 CLOSE cursor_name;,然后 END PROCEDURE
MySQL 游标没有“是否还有下一行”的预判机制。FETCH 执行后,只有下一次 FETCH 触发 NOT FOUND 异常才会设置 done。所以如果在 FETCH 后没立刻判断 done,而是先做业务逻辑,就会对空行重复处理,甚至触发错误(比如 NULL 写入非空字段)。
FETCH 必须紧跟 IF done THEN ... 判断,中间不能插其他语句DECLARE done INT DEFAULT FALSE; 要放在变量声明区,且不要在循环中反复 SET done = 0 —— 这会覆盖异常触发的 done = TRUE
FETCH(比如嵌套游标),每次都要单独配 done 变量和 handler,不能复用遇到内存暴涨或过程卡死,第一反应不该是重写逻辑,而是确认是不是 sp_head::main_mem_root 没释放。MySQL 不支持断点调试,但 performance_schema 能直接暴露内存归属。
SELECT EVENT_NAME, CURRENT_NUMBER_OF_BYTES_USED FROM performance_schema.memory_summary_global_by_event_name WHERE EVENT_NAME LIKE 'memory/sql/sp_head%';
CURRENT_NUMBER_OF_BYTES_USED 持续增长且远超预期(比如 >512MB),基本锁定是游标未关闭或异常退出SHOW PROCESSLIST 看是否有状态为 Sending data 或 executing 的长连接,用 KILL QUERY [id] 中断(不是 KILL [id])performance_schema:SET GLOBAL performance_schema = OFF;,但只是掩耳盗铃,不解决根本问题CLOSE 当成和 OPEN 对称的强制操作,写在所有出口之前,而不是“记得就加,忘了就算”。 Tplink企业版路由器WiFi名称的默认设置介绍(Tplink企业版路由器WiFi名称的默认设置是什么)
Tplink路由器灯常亮无法上网的原因分析(如何解决Tplink路由器灯常亮无法上网的问题)
Tplink千兆企业级路由器自动重启的作用和优势介绍(如何设置Tplink千兆企业级路由器自动重启功能)
一根天线的tplink路由器有哪些(一根天线的Tplink路由器的特点和优势介绍)
tplink路由器外网访问不了nas(Tplink路由器外网访问NAS的原因分析)
Tplink无法搜到路由器的原因分析(如何解决Tplink无法搜到路由器的问题)