从7.9G到5.9G:我们如何揪出MySQL内存泄漏的“真凶”

20 阅读3分钟

引言:一个令人惊醒的噩梦

有这么一句话是这么讲的,容器当中最为奇特的漏洞,通常来自你最为信赖的系统。8G的容器运行着MySQL,MySQL的RSS猛涨到了7.9G,重启有效果但是3小时后又再次出现——这就是那种能让人在半夜,吓得冷汗直冒惊醒的噩梦。

问题的扎心之处在于:一切看起来都很正常,却活生生地在吃你的内存。

现象:诡异的“幽灵”内存贼

容器分配8G,MySQL进程RSS占用7.9G,系统告急。

重启一下,内存瞬间掉下来,但仨小时后又开始蹦跶,周而复始。

这不是偶发,这是规律性的内存缓慢泄漏,最后的结局就是OOM Kill。好家伙,这不就是标准的“死亡漩涡”吗?一开始没人在意,等到容器崩溃,大家才开始挠头。

定位:Buffer Pool只有4G,5G去哪了?

当运用 SHOW ENGINE INNODB STATUS 来查看时,让人颇为惊讶,发现InnoDB Buffer Pool居然只有4G,可配置里明明写的是更大的,那余下的2-3G内存到底被谁占用了。

这时候就得祭出终极大招:查看memory_summary_global_by_event_name,看看各个模块到底占了多少。

一查不要紧,前三名全是error_log相关的条目。

这一刻,元凶浮出水面。

根因:性能观测表的“黑吃黑”

原本是MySQL 8.0所引入的新特性performance_schema.error_log表,它专门用来记录错误日志。这个物件配置得比较激进,记录级别是ALL再加上高频警告——换个说法,只要MySQL有任何动静,都会把记录内容存储到这张表格之中。

随着时光流逝,这个表就好像一个无底洞一样,不断地吞食内存。更叫人苦恼的是,这些数据并不会自己清理,就那样待在内存里变成僵尸数据。

容器场景下这个问题尤其致命,因为你根本看不到硬盘,所有东西都吃内存。

验证:杀人证据确凿

执行这条SQL:

SELECT * FROM performance_schema.memory_summary_global_by_event_name 
ORDER BY CURRENT_NUMBER_OF_BYTES_USED DESC LIMIT 10;

结果已经出来,error_log相关的内存,占用排在前三位,证据都明明白白的,不用再去找寻——这就好像监控摄像头直接拍到了元凶作案的过程似的。

解决:一条命令,2G内存瞬间回落

SET GLOBAL log_error_verbosity=2;

这一条把日志记录级别从ALL降到WARNING以上,一瞬间收获医生“救命”般的快乐。

再跑一条:

TRUNCATE performance_schema.error_log;

把历史数据全清空。

效果?内存从7.9G直接跳水到5.9G,简直是见鬼了。

这就是为什么重启管用——重启后这张表也被清空了。

预防:一行配置省30%内存

根本不想再经历这噩梦?把这行写进my.cnf就完事了:

[mysqld]
performance_schema_consumer_events_errors_log = OFF

或者更直接的做法:

[mysqld]
performance_schema = OFF

对于容器场景,这能直接省掉30%左右的内存占用。

不需要性能观测?关掉它。

需要?那就想办法把error_log的记录级别调低,在需要排查问题的时候调高记录级别。

这个坑的启示很简单:监控工具本身也会变成性能杀手。新特性默认启用是为了方便,但在资源受限的容器里,这就是个定时炸弹。

定期对performance_schema的内存占用进行检查,降低log_error的清晰程度,关闭不需要的消费者,这三个办法就能够让你放心了,再也不要在半夜为MySQL OOM担心得冒汗了。

声明

声明,此文章中九成是我本人原创,仅有极少量素材借助AI来处理,并且所有内容我都已仔细检查,相关案例依照MySQL官方文档以及真实线上场景而来,该文章旨在传播正能量,不存在低俗不良引导,期望读者可以理解。