mysql内存随运行时间持续上涨且重启回落,90%以上源于内存碎片累积或innodb_buffer_pool_size等配置与负载不匹配;其本质是glibc分配器释放内存后未归还os,而performance_schema无法监控缓冲池页帧等关键内存消耗。

MySQL内存随运行时间上涨,不是“一定有问题”,但持续上涨且不回落,大概率是内存碎片累积或配置与负载不匹配导致的隐性失控。重启能回落、日常无大查询却越跑越高——这种典型现象,90%以上跟碎片和缓存分配机制有关,而不是代码写错了。
innodb_buffer_pool_size 动态调整后内存不释放?别信“自动回收”
innodb_buffer_pool_size 是 MySQL 最大内存消费者,但它本身不会“主动归还”已分配的物理内存给系统。Linux 的 malloc(尤其是 glibc 默认分配器)在释放内存时,往往只把页归还给进程堆管理器,而非操作系统——尤其当高水位后有少量活跃页穿插其中时,整块内存无法 madvise(MADV_DONTNEED)。
-
innodb_buffer_pool_size支持在线调小(MySQL 5.7+),但必须分步操作:先设为一个明显偏小的值(如从 32G → 2G),等几秒让缓冲池收缩完成,再调回目标值(如 28G)。这会强制触发底层内存重分配,把旧碎片空间交还 OS。 - 直接调大不会立即生效,需等后台线程逐步加载页;但调小会立刻触发清理,是唯一不用重启就能“挤出”碎片内存的操作。
- 注意:
innodb_buffer_pool_instances必须整除新大小(如设 28G 时,实例数不能是 16,因 28×1024÷16 = 1792MB 不是整数),否则实际生效值会被向下取整,导致误判。
为什么 performance_schema 查不到“吃内存的坏人”?
performance_schema.memory_summary_global_by_event_name 只统计走 PFS 分配路径的内存,而以下几类不在此列:
- InnoDB 缓冲池页帧本身(buf_buf_pool 统计的是元数据,不是页内容占用)
- 复制线程的 relay log buffer、gtid_executed 集合、group replication 的消息缓存
- 查询解析阶段的 JIT 内存(MySQL 8.0.31+ 启用时)、JSON 文档解析临时结构
- 使用 jemalloc 或 tcmalloc 替代 glibc 时,其内部 arena 碎片也不上报
如果 SELECT SUM(current_number_of_bytes_used) FROM performance_schema.memory_summary_global_by_event_name 结果远小于 ps aux 显示的 VmRSS,说明大量内存消耗发生在 PFS 监控盲区——这时应优先怀疑分配器碎片或复制/GR 插件开销。
连接数没爆,为什么线程相关内存还在涨?
每个连接预分配的内存不止thread_stack,还包括:
- sort_buffer_size、join_buffer_size、read_buffer_size 等 per-thread 缓冲区,只要连接存在就常驻
- tmp_table_size 和 max_heap_table_size 虽然按需使用,但一旦某连接创建过内存临时表,其分配过的最大尺寸可能被缓存复用,不会随连接 Sleep 自动归还
- 应用端未正确 close() 连接,但 MySQL 因 wait_timeout 未到仍维持 Sleep 状态,这些连接的 buffer 全部挂着
检查真实活跃连接数用:SHOW STATUS LIKE 'Threads_connected';
对比操作系统级连接数:ss -tan | grep :3306 | wc -l;
若后者显著大于前者,说明有连接绕过 MySQL 协议层(比如代理直连、异常断连未 cleanup),这类连接的内存完全不受 MySQL 内部管理。
真正难处理的,是那些“没做错什么,但就是越跑越胖”的情况——碎片不报警、PFS 不显示、连接数正常、SQL 也无慢日志。这时候得接受一个事实:MySQL 的内存模型本就不追求“精确归还”,它更倾向“稳住水位、避免频繁 syscalls”。调参只是边界控制,定期观察 VmRSS 趋势 + 关键参数水位 + 分配器选型(换 jemalloc 常能延缓碎片爆发),比指望某条命令一键清空更现实。











