mysql函数内存溢出或超时主因是sp_head::main_mem_root持久驻留、tmp_table_size不足或结果集过大;需通过performance_schema监控内存、避免大结果集+用户变量组合、统一tmp_table_size与max_heap_table_size、应用层设置sockettimeout等综合优化。

MySQL函数执行时内存溢出或响应超时,基本不是函数本身“写错了”,而是它触发了底层内存管理机制的临界点——尤其是 sp_head::main_mem_root 持久驻留、tmp_table_size 不足、或单次计算结果集过大。直接调大内存参数往往治标不治本,得从执行上下文和资源生命周期入手。
查清是不是函数真在吃内存:盯住 memory/sql/sp_head::main_mem_root
函数(包括存储过程、触发器)不释放 sp_head::main_mem_root 是 MySQL 5.7+ 的确定行为。这个内存块一旦分配,就一直挂着,直到整个存储程序执行结束;如果函数里嵌套循环、拼接大字符串、或反复 SELECT ... INTO @var,内存会线性累积。
- 先确认 performance_schema 是否开启内存监控:
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',若超过 512MB 且持续上涨,基本锁定是函数/触发器导致 - 别只看总量——用
SHOW PROCESSLIST找出 State 为executing或Sending data的长时连接,KILL QUERY [id]中断后观察该值是否回落
函数里别用大结果集 + 用户变量组合
这是最隐蔽的爆内存写法:SELECT col1, col2 INTO @v1, @v2 FROM huge_table WHERE ...。MySQL 会把整张结果集加载进 main_mem_root,哪怕你只取一行,只要查询计划走全表扫描或没索引,照样崩。
- 改用显式游标并控制 FETCH 数量:声明
DECLARE cur CURSOR FOR SELECT id FROM t WHERE status=1 LIMIT 1000,再配合FETCH cur INTO @id循环处理 - 避免
@blob := LOAD_FILE(...)或@json := JSON_OBJECT(...)构造超长内容;如必须,用CONVERT(... USING utf8mb4)显式截断 - 函数内慎用子查询返回多行,例如
(SELECT GROUP_CONCAT(name) FROM users WHERE dept_id = @dept)—— 改成临时表 +INSERT ... SELECT分步做
tmp_table_size 和 max_heap_table_size 必须配对调
函数里一旦涉及隐式临时表(如 GROUP BY、DISTINCT、ORDER BY 非索引字段),MySQL 就会先尝试建内存临时表。两个参数不一致会导致“明明设了 256MB,却在 16MB 就落盘”,而磁盘临时表又慢又耗 IO,间接拉长执行时间触发超时。
- 检查当前值:
SELECT @@tmp_table_size, @@max_heap_table_size,二者必须相等,否则以较小者为准 - 线上建议设为统一值,例如
SET GLOBAL tmp_table_size = 128*1024*1024; SET GLOBAL max_heap_table_size = 128*1024*1024; - 注意:该设置对已存在的连接无效,新连接才生效;若函数由应用层长连接调用,需重启连接或让应用主动重连
超时不是配置 wait_timeout 就能解决的
函数执行超时(ERROR 2013 / ERROR 2006)通常不是连接空闲超时,而是函数内部某一步卡死——比如等待锁、磁盘临时表写满、或 main_mem_root 分配失败后重试耗尽 CPU 时间。
-
max_execution_time对函数无效,它只作用于 SELECT 语句;函数内逻辑超时只能靠外部控制 - Java 应用侧务必设置 JDBC URL 参数:
connectTimeout=5000&socketTimeout=30000,比 MySQL 层的net_read_timeout更可靠 - 函数开头加兜底:
DECLARE CONTINUE HANDLER FOR SQLEXCEPTION BEGIN CLOSE cur_name; LEAVE proc_label; END;,防止异常时游标不关、内存不释放
真正难处理的从来不是单次函数崩溃,而是 sp_head::main_mem_root 在并发调用下像滚雪球一样越积越多——它不进缓冲池统计,也不受 innodb_buffer_pool_size 约束,监控盲区最大。上线前务必用 performance_schema.memory_summary_by_thread_by_event_name 抓一次压测期间的线程级内存分布,否则优化就是蒙眼走路。











