一聚教程网:一个值得你收藏的教程网站

热门教程

如何关闭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 STATUSInnodb_buffer_pool_reads 可能很低,但 performance_schema.memory_summary_global_by_event_namememory/sql/sp_head::main_mem_root 却飙到几 GB。

  1. 游标声明后必须显式 OPEN,也必须显式 CLOSE;仅靠存储过程结束自动清理不可靠
  2. 异常路径(比如 SQLEXCEPTION)下 CLOSE 容易被跳过,必须用 DECLARE CONTINUE HANDLER FOR SQLEXCEPTION 包一层
  3. CLOSE 放在 LEAVE 之后、循环外,是常见错误;正确位置应在所有退出路径前,包括正常结束和异常分支

如何确保每个退出路径都执行 CLOSE

最稳妥的方式是在存储过程开头定义一个标签(如 proc_exit),再把 CLOSELEAVE 统一收口到该标签下。避免在循环体里零散写 CLOSE,也别依赖“最后写一句 CLOSE”这种看似简洁实则危险的做法。

  1. DECLARE CONTINUE HANDLER FOR SQLEXCEPTION BEGIN CLOSE cursor_name; LEAVE proc_exit; END;
  2. 正常流程走到末尾时,也跳转到 proc_exit 标签,而不是直接写 END
  3. 循环内部只做 FETCH 和业务逻辑,IF done THEN LEAVE read_loop;,不放 CLOSE
  4. proc_exit: 标签下只放 CLOSE cursor_name;,然后 END PROCEDURE

FETCH 后立刻检查 done,否则可能多 fetch 一次

MySQL 游标没有“是否还有下一行”的预判机制。FETCH 执行后,只有下一次 FETCH 触发 NOT FOUND 异常才会设置 done。所以如果在 FETCH 后没立刻判断 done,而是先做业务逻辑,就会对空行重复处理,甚至触发错误(比如 NULL 写入非空字段)。

  1. FETCH 必须紧跟 IF done THEN ... 判断,中间不能插其他语句
  2. DECLARE done INT DEFAULT FALSE; 要放在变量声明区,且不要在循环中反复 SET done = 0 —— 这会覆盖异常触发的 done = TRUE
  3. 若循环中需多次 FETCH(比如嵌套游标),每次都要单独配 done 变量和 handler,不能复用

调试时别只看逻辑,先查 memory_summary_global_by_event_name

遇到内存暴涨或过程卡死,第一反应不该是重写逻辑,而是确认是不是 sp_head::main_mem_root 没释放。MySQL 不支持断点调试,但 performance_schema 能直接暴露内存归属。

  1. 执行 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%';
  2. 如果 CURRENT_NUMBER_OF_BYTES_USED 持续增长且远超预期(比如 >512MB),基本锁定是游标未关闭或异常退出
  3. SHOW PROCESSLIST 看是否有状态为 Sending dataexecuting 的长连接,用 KILL QUERY [id] 中断(不是 KILL [id]
  4. 临时缓解可关 performance_schemaSET GLOBAL performance_schema = OFF;,但只是掩耳盗铃,不解决根本问题
游标资源泄漏最难缠的地方不在语法错,而在“看起来运行成功了”。表数据插进去了、日志打出来了、过程返回了 success,但后台内存已经悄悄堆满——直到某次并发高峰突然崩掉。真正可靠的写法,是把 CLOSE 当成和 OPEN 对称的强制操作,写在所有出口之前,而不是“记得就加,忘了就算”。

热门栏目