答案是sp_head::main_mem_root内存未释放所致;需查performance_schema中memory/sql/sp_head::main_mem_root用量超1gb即确认,且必须显式close游标、加异常处理、避免大结果集fetch。

查 performance_schema 确认是不是 sp_head::main_mem_root 在吃内存
MySQL 存储过程不报“内存溢出”错误,但 sp_head::main_mem_root 内存区持续增长会直接触发 OOM Killer 杀掉 mysqld。这不是配置调小了就能解决的问题,得先确认是不是它在作祟。
执行前确保监控已启用:SELECT VARIABLE_VALUE FROM performance_schema.global_variables WHERE VARIABLE_NAME = 'performance_schema_instrument',返回值里必须含 memory/% = COUNTED;否则所有后续查询都无效。
然后运行:SELECT EVENT_NAME, CURRENT_NUMBER_OF_BYTES_USED FROM performance_schema.memory_summary_global_by_event_name WHERE EVENT_NAME = 'memory/sql/sp_head::main_mem_root'
- 如果返回值 > 1GB(尤其远超你的
innodb_buffer_pool_size),基本就是它 - 配合
SHOW PROCESSLIST看有没有长期卡在executing或Sending data的连接,且 SQL 含DECLARE CURSOR FOR SELECT -
dmesg -t | grep -i "killed process.*mysqld"出现,但SHOW STATUS LIKE 'Innodb_buffer_pool_reads'并不高 → 说明不是 InnoDB 层问题,而是 SQL 层失控
检查游标是否漏关或异常路径未覆盖
MySQL 不会在存储过程退出时自动释放游标占用的 sp_head::main_mem_root 内存。哪怕你用 LEAVE 跳出循环,只要没 CLOSE cursor_name,这块内存就永远挂着。
- 每个
DECLARE CURSOR cursor_name后,必须紧跟着配对的CLOSE cursor_name,且写在END之前 - 必须加异常处理:
DECLARE CONTINUE HANDLER FOR SQLEXCEPTION BEGIN CLOSE cursor_name; LEAVE proc_label; END; - 如果游标在嵌套块(如
BEGIN ... END)中声明,CLOSE也必须写在同级块内,跨块无效 - 别信“过程结束自动清理”——这是 MySQL 5.7/8.0 都没实现的假定
看临时表和视图是否引发隐式物化与全量排序
临时表没建索引、视图含 ORDER BY 或聚合,都会导致 MySQL 强制物化中间结果集,把几十万行全塞进内存再操作。
-
EXPLAIN查视图时若出现select_type = DERIVED,说明被当成派生表加载进内存 - 视图定义里有
ORDER BY created_at DESC,而外层又加了LIMIT 10→ 排序发生在截断前,必须全排 -
CREATE TEMPORARY TABLE ... SELECT会跳过索引定义阶段,应拆成CREATE TEMPORARY TABLE+CREATE INDEX+INSERT INTO ... SELECT - CTE(
WITH)不是轻量替代:MySQL 8.0 默认强制物化,被引用多次时更占内存,比带索引的临时表还危险
验证大表 JOIN 是否触发哈希表内存爆炸
存储过程中大表硬 JOIN 报内存溢出,90% 是因为 MySQL 被迫把整个右表加载进内存建哈希表。调 join_buffer_size 治标不治本,还可能引发并发 OOM。
- 适用前提:左右表都有可排序字段(如自增
id、created_at) - 改用分批主键拉取:
SELECT id FROM orders WHERE id > ? ORDER BY id LIMIT 1000,再用这批id精准拉右表 - 禁用
LIMIT OFFSET分页,它会扫描并丢弃前面所有行,加剧内存压力 - 避免在游标里做
FETCH大批量数据:一次拉 10 万行,main_mem_root可能瞬间吃掉几百 MB
真正难缠的不是某一行代码写错了,而是多个看似无害的操作叠加后,让 sp_head::main_mem_root 在后台无声膨胀——它不走任何缓存策略,也不随会话释放,只等某次触发把它推过临界点。











