
问
在 第44问 中, 我们使用 tcmalloc 提供的工具, 来查看 MySQL 的内存分配
该方法对性能影响不大, 可以在生产环境运行, 但需要将 MySQL 的分配器配置成 tcmalloc
在本次实验中, 我们介绍另外一种方法, 针对于 MySQL 的内存突增情况进行诊断
实验
我们依然宽油起一个数据库:

本实验中, 我们需要模拟MySQL的内存突增的情况. 我们从 MySQL 的 bug 库里找到一个易于复现的相关 bug: https://bugs.mysql.com/bug.php?id=99382
这个 bug 的描述很清晰, 并提供了一个 SQL 脚本, 直接执行该脚本就可以复现内存激增的情况:

我们来试一下:

当前 MySQL 的内存占用为181M
执行脚本:

在脚本执行过程中, 我们会发现 MySQL 使用的内存在不断上涨

现在我们来诊断 MySQL 对内存的使用. 在脚本执行过程中, 同时执行如下命令进行观测:

小贴士
什么是系统调用mmap?
简单来说, MySQL不是直接向Linux申请内存, 而是向glibc申请内存. glibc会维持一个内存池, 当glibc发现内存池吃紧时, 会通过系统调用mmap(或者brk)向Linux申请内存.
所以我们监听系统调用mmap, 也就监听了MySQL在什么情况下需要大量内存 (即什么时候glibc的内存池吃紧了)
至于glibc内存分配的机制, 可参考阅读: https://sploitfun.wordpress.com/2015/02/10/understanding-glibc-malloc/
可以看到在当前目录下生成了 perf.data :

我们将 perf.data 转换成可读的方式:

简单看一下 perf.out , 我们会看到每次 mmap 的调用都记录了足够的信息, 包括:
-
图中红色标记的部分, 是要求分配内存的线程号. 有了线程号, 我们就可以找到是哪个SQL在占用内存. -
图中蓝色标记的部分, 是分配内存的大小 -
图中绿色标记的部分, 是分配内存时的堆栈信息
有了线程号和堆栈信息, 我们就可以判断是哪个 SQL(或者 MySQL 的哪个内部线程), 在什么情况下要求分配内存
本例中, 我们判断29735号进程, 在 create view 这个操作中, 打开表时需要分配内存
当然, perf.out 文件很长, 大家需要将信息都聚合在一起, 再判断谁是最消耗内存的
局限
本实验所介绍的方法是有局限的: 本方法适用于 MySQL 内存激增的情况, 在其他情况下 (比如内存缓慢并持续增长), 本方法不一定能观测到准确的信息.
如小贴士所述,MySQL 向 glibc 申请内存 (第一步), glibc 的内存池吃紧时,glibc 再向 Linux 申请内存 (第二步).
在内存激增的情况下, 第一步的发生 大概率会导致 第二步的发生, 我们观测系统调用 mmap , 实际上是观察到了第二步的发生, 推导出第一步的原因.
而在内存缓慢增长的情况下, 第一步的发生 小概率会导致 第二步的发生, 导致无法从第二步推导第一步.
比如: 由于某个原因, 内存缓慢增长, 当 glibc 的内存池即将吃紧时, 发生了其他业务 SQL , 导致这些业务 SQL 触发了 glibc 内存池吃紧, 那么我们诊断出内存增长的原因是因为业务 SQL , 而真正的原因被藏了起来.
使用本方法时, 希望大家能注意到这个局限.
关于 MySQL 的技术内容,你们还有什么想知道的吗?赶紧留言告诉小编吧!
