mysql内存持续增长大概率源于配置不当或查询失控,而非泄漏;需警惕重启后迅速复现、与流量不匹配的不可逆增长,核心问题包括buffer pool命中率低、连接堆积、临时表/排序内存滥用及glibc arena碎片等。

MySQL内存占用持续增长,大概率不是“泄漏”,而是配置不当或查询行为失控导致的资源累积。真正需要警惕的是增长不可逆、重启后迅速复现、且与业务流量不匹配的部分。
innodb_buffer_pool_size 配置过大但命中率低
这个参数是 MySQL 内存占用的绝对大头,但它只在被有效使用时才合理。设了 48G,结果 SHOW ENGINE INNODB STATUS\G 里 Buffer pool hit rate 长期低于 95%,说明大量缓存页没被访问过,却一直占着物理内存不释放。
- Buffer Pool 在启动后会预分配(尤其开启
innodb_buffer_pool_dump_at_shutdown时),但不会随负载下降自动收缩 - MySQL 5.7+ 支持动态调整
innodb_buffer_pool_size,但必须是 1MB 对齐,且要同步调innodb_buffer_pool_instances,否则实际生效值会被截断 - 检查是否误把混部机器当专用库:64G 总内存配 50G Buffer Pool,Redis + Nginx + 日志服务全得抢剩下的 14G
连接数堆积 + 线程私有内存未回收
每个连接都会分配独立内存:thread_stack(默认 256KB–2MB)、sort_buffer_size、join_buffer_size 等。这些不是共享池,是 per-connection 的,连接不关,内存就不退。
-
SHOW PROCESSLIST里大量Sleep状态,Time列从几百秒涨到几万秒 → 应用没 close 连接,或连接池 idle-timeout 设得太长 -
wait_timeout和interactive_timeout默认 28800 秒(8 小时),线上建议压到 60–300 秒 - PHP 的
mysql_connect()或 Java 的 JDBC URL 没加?useSSL=false&connectTimeout=3000类参数,网络抖动后连接卡住不释放
临时表和排序操作反复申请内存
这类内存不计入 VmRSS 的 InnoDB 缓冲池统计,却真实吃掉物理内存,且容易被忽略。
-
Created_tmp_tables和Created_tmp_disk_tables差值变小 → 越来越多查询被迫写磁盘临时表,说明tmp_table_size和max_heap_table_size设置过小,但每次建内存临时表仍先按上限预分配 - 一个
GROUP BY大字段 +ORDER BY的查询,可能同时触发sort_buffer_size、read_rnd_buffer_size、内存临时表三重分配 -
performance_schema.memory_summary_global_by_event_name中memory/temptable/physical_ram或memory/sql/Filesort_buffer排名靠前,就是它在作祟
glibc malloc arena 和透明大页(THP)干扰
这不是 MySQL 代码问题,而是底层内存管理机制导致的“假高”或“难释放”。
- 多线程环境下 glibc 使用多个 malloc arena,内存不会轻易归还 OS,
cat /proc/$(pidof mysqld)/status | grep -i vm看VmSize远大于VmRSS,就可能是 arena 碎片 -
cat /sys/kernel/mm/transparent_hugepage/enabled返回[always]→ THP 会让 MySQL 分配大页后更难释放,容器环境尤其明显,应改为never或madvise - MySQL 8.0+ 默认关闭
innodb_use_sys_malloc,改用内置内存管理器,但若启用了插件(如 audit_log、rocksdb 引擎),仍可能绕过该控制
真正难排查的,往往是 Buffer Pool 看似合理、连接数也正常、performance_schema 统计又没爆表,但 VmRSS 每天涨 2GB —— 这时候得盯住 /proc/<pid>/smaps</pid> 里 Anonymous 和 Heap 区域的增长曲线,再结合应用侧慢查日志交叉比对。











