MySQL内存增长过快:performance_schema统计与实际物理内存差值排查
内存差值的常见占用场景
performance_schema(以下简称PFS)的内存统计不是全量覆盖的,默认配置下大量内存分配不会被计入memory_summary_global_by_event_name,加上进程层面的内存统计和MySQL自身统计的口径差异,13G的差值通常来自以下几类场景:
- 未被PFS监控的MySQL自有内存分配
MySQL默认不会开启所有memory类的instrument,5.7、8.0版本默认关闭的内存监控项覆盖了很多核心模块:包括InnoDB部分后台线程的内存分配、内存临时表的运行时占用、每个连接线程的栈内存、binlog/relay log的线程级缓存、MGR/半同步复制的消息缓存、自适应哈希索引的部分内存块、表定义缓存/表打开缓存的元数据占用、自定义插件/UDF的内存分配。如果实例连接数很高、表数量极多、用了MGR架构,这部分未统计的内存加总到10G以上非常常见。 - 内存分配器的开销与未归还内存
ps、top看到的RSS是进程持有的全部物理内存页,不管页里存的是有效数据还是碎片、空闲块。MySQL用的glibc ptmalloc、jemalloc等内存分配器,本身会维护arena结构、分配元数据,还会留存大量已被MySQL释放的内存块不还给操作系统,用来应对后续的分配请求——长运行的实例上,这部分碎片+留存空闲内存占个几G到十几G是很普遍的情况,这部分内存MySQL自身已经标记为释放,自然不会被PFS统计到。 - PFS自身的内存占用
PFS自己运行需要的内存,比如事件记录表、锁结构、统计哈希表的内存,如果没开启memory/performance_schema/%相关的instrument,也不会计入统计结果。如果PFS的max类参数(比如performance_schema_max_table_instances、performance_schema_max_statement_classes)设置得过大,PFS自身占几G内存很正常。 - 特殊场景的遗漏统计
比如大查询运行时的上下文、join/sort等操作的临时buffer未被对应instrument捕获,主从场景下SQL线程的大事务缓存,审计插件、防火墙插件等第三方组件的内存占用,都不会进入默认的PFS内存统计。
具体排查方法
- 第一步:补全PFS内存监控覆盖
先确认所有内存类instrument的开启状态,执行:
动态开启所有内存监控(该操作对性能影响极低,生产环境可安全执行):SELECT NAME, ENABLED FROM performance_schema.setup_instruments WHERE NAME LIKE 'memory/%' AND ENABLED = 'NO';
等待业务跑1~2小时后,重新统计UPDATE performance_schema.setup_instruments SET ENABLED = 'YES', TIMED = 'YES' WHERE NAME LIKE 'memory/%';memory_summary_global_by_event_name的总内存,这时候的结果基本能覆盖所有MySQL自身主动分配的内存,和RSS的差值就只剩分配器层面的开销了。 - 第二步:排查分配器层面的碎片与留存内存
先拿到mysqld的进程PID,查看进程的详细内存段分布:
重点看匿名段(anon列占用高的行),如果堆段的RSS远大于PFS统计的总内存,基本可以确定是内存分配器留存的空闲内存或者碎片。pmap -x <mysqld_pid> | sort -k3 -nr | head -20
如果用的是glibc ptmalloc,可以在8.0及以上版本MySQL里执行CALL sys.malloc_stats();,或者gdb attach到进程调用malloc_stats(0),查看各个arena的已用、空闲、碎片占比;如果是jemalloc/tcmalloc,可以开启内置的heap profile功能,导出内存分配栈定位具体的分配来源。 - 第三步:核算固定+会话内存的理论占用
先算全局固定内存的总和:innodb_buffer_pool_size + innodb_log_buffer_size + key_buffer_size + 表定义缓存元数据内存 + 其他全局缓存类参数配置值,再算会话级的理论峰值:max_connections * (join_buffer_size + sort_buffer_size + read_buffer_size + read_rnd_buffer_size + thread_stack),如果理论总和已经接近24G,说明是参数配置过高,尤其是会话级buffer设得太大,连接数上来之后累计占用过高。 - 第四步:排查特殊功能的内存占用
检查是否开启了MGR、审计插件、企业版防火墙等功能,这类组件的内存很多默认不在PFS统计范围内;检查performance_schema_max_*系列参数,确认PFS自身的内存配置是否过大;检查是否存在长期未提交的大事务、超大结果集查询,这类操作会持有大量临时内存不释放。 - 第五步:跟踪内存增长趋势
用pidstat -r -p <mysqld_pid> 5持续观测内存变化,如果差值是实例启动后就固定存在,大概率是未监控的固定内存分配;如果差值随运行时间持续上涨,重启后回落,大概率是存在内存泄漏或者持续累积的碎片,需要结合内存profile工具定位具体的分配点。
注意:ps/top统计的RSS包含少量共享库的驻留页,但这部分占比通常只有几十M,不会造成13G这么大的差值,不需要作为排查重点。
内容的提问来源于stack exchange,提问作者springs
相关产品推荐
相关产品推荐

