答案是sp_head::main_mem_root内存池被游标和临时表共享占用,而非tempdb或innodb临时表空间;查performance_schema中memory/sql/sp_head::main_mem_root超1gb即确认,其不走tmp_table_size控制、不写磁盘,只硬占ram,且close游标可批量清理而drop临时表不能释放sql层上下文内存。

游标本身不会导致Temp表溢出,但游标常和临时表混用,且二者共享同一内存根源:sp_head::main_mem_root。 真正吃光内存的是这个SQL层内存池,不是tempdb或InnoDB临时表空间。
查不到Temp表日志,但RSS内存狂涨
执行含 DECLARE cursor_name CURSOR FOR SELECT ... 的存储过程时,如果 SHOW STATUS LIKE 'Created_tmp_disk_tables' 没明显上涨,但系统RSS内存持续飙升、dmesg 报 Kill process XXX (mysqld),大概率是 sp_head::main_mem_root 在撑爆物理内存——它不走 tmp_table_size 控制,也不写磁盘,就硬占RAM。
- MySQL 5.7/8.0 中,游标遍历的每一行数据(包括字段值、表达式结果、变量赋值)都堆在
sp_head::main_mem_root上,不复用、不释放 - 临时表(
CREATE TEMPORARY TABLE或隐式生成)若在同一个存储过程中创建,其元数据和部分行缓存也落在这块内存里 -
performance_schema.memory_summary_global_by_event_name里查memory/sql/sp_head::main_mem_root,超 1GB 就该警觉
为什么CLOSE游标能缓解,但CREATE TEMPORARY TABLE不能?
CLOSE cursor_name 会触发 main_mem_root 上游标相关内存块的批量清理;而 DROP TEMPORARY TABLE 只释放引擎层结构,其背后依赖的SQL执行上下文(比如游标已读过的行缓存、中间计算变量)仍挂在 sp_head 里,直到存储过程彻底退出。
- 游标未
CLOSE→main_mem_root中的行缓存一直驻留 - 临时表已
DROP→ 表结构释放了,但建表前的SELECT ... INTO @var或循环中拼接的字符串还在main_mem_root里 - 嵌套块中声明游标,
CLOSE必须在同级块内,否则语法报错或实际不生效
别让FETCH变成内存炸弹
单次 FETCH cursor_name INTO var1, var2 不是“取一行”,而是把整行字段内容拷贝进 main_mem_root —— 如果源查询返回 10 万行,又没分批 FETCH + CLOSE,这块内存就线性吃掉几百MB。
- 避免
DECLARE cur CURSOR FOR SELECT * FROM huge_table这类无过滤全扫 - 业务允许时,把游标逻辑移到应用层流式处理,数据库只做带
LIMIT/WHERE的分页查询 - 必须用游标时,在循环内加计数器,每 1000 行
COMMIT并CLOSE+ 重新OPEN(需确保逻辑可重入)
最易被忽略的一点:即使你没显式建任何临时表,只要存储过程里有游标 + 大量字符串拼接、JSON处理、或触发器链加载,sp_head::main_mem_root 就可能悄悄突破 4GB —— 它不报警,只等 OOM Killer 动手。











