最新下载
热门教程
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
如何关闭MySQL存储过程游标避免资源泄漏
时间:2026-08-30 09:02:49 编辑:袖梨 来源:一聚教程网
答案是sp_head::main_mem_root内存未释放所致;需查performance_schema中memory/sql/sp_head::main_mem_root用量超1GB即确认,且必须显式CLOSE游标、加异常处理、避免大结果集FETCH。
游标没 CLOSE 就退出,内存会一直挂着
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之后、循环外,是常见错误;正确位置应在所有退出路径前,包括正常结束和异常分支
如何确保每个退出路径都执行 CLOSE
最稳妥的方式是在存储过程开头定义一个标签(如 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
FETCH 后立刻检查 done,否则可能多 fetch 一次
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,不能复用
调试时别只看逻辑,先查 memory_summary_global_by_event_name
遇到内存暴涨或过程卡死,第一反应不该是重写逻辑,而是确认是不是 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 对称的强制操作,写在所有出口之前,而不是“记得就加,忘了就算”。