首先,讲一下MySQL的内存消耗分为:
- 会话级别的内存消耗:如 sort_buffer_size 等,每个会话都会开辟一个 sort_buffer_size 来进行排序操作。
- 全局的内存消耗:例如 innodb_buffer_pool_size 等,全局共享的内存段。
关于会话级的内存消耗解释如下:
read_buffer_size,sort_buffer_size,read_rnd_buffer_size,tmp_table_size这些参数在需要的时候才分配,操作后释放。这些会话级的内存,不管使用多少都分配该size的值,即使实际需要远远小于这些size。每个线程可能会不止一次需要分配buffer,例如子查询,每层都需要有自己的read_buffer,sort_buffer,tmp_table_size等。找到每次内存消耗峰值是不切实际的,因此我的这些建议可以用来衡量一下你实际修改一些变量值产生的反应,例如把sort_buffer_size从1MB增加到3MB,并且在max_connections为1000的情况下,内存消耗增长峰值并不是你所计算的3000MB而是30MB。
MySQL内存计算器:http://www.mysqlcalculator.com
一、背景
我的朋友小明了,最近遇到了一个郁闷的问题:明明OS还有大量的空闲内存,可是却发生了SWAP,百思不得其解,她就来找我搬救兵了。先看下SWAP是干嘛的,了解下它的背景知识。
在Linux下,SWAP的作用类似Windows系统下的“虚拟内存”。当物理内存不足时,拿出部分硬盘空间当SWAP分区(虚拟成内存)使用,从而解决内存容量不足的情况。
SWAP意思是交换,顾名思义,当某进程向OS请求内存发现不足时,OS会把内存中暂时不用的数据交换出去,放在SWAP分区中,这个过程称为SWAP OUT。当某进程又需要这些数据且OS发现还有空闲物理内存时,又会把SWAP分区中的数据交换回物理内存中,这个过程称为SWAP IN。在vmstat的输出结果中,分别表现为 si\so 两列,如下图:
看到这里我们就知道了,发生SWAP的最直接可能的原因是进程向OS申请内存时,发现物理内存不足,当没有SWAP可用的话,这时可能会一直等待,也可能会触发 OOM-killer 机制,OS把消耗内存最多的那个进程kill掉以释放内存,这个选择取决于内核参数vm.swappiness。
vm.swappiness是操作系统控制物理内存交换出去的策略。它允许的值是一个百分比的值,最小为0,最大运行100,该值默认为60。vm.swappiness设置为0表示尽量少swap,100表示尽量将inactive的内存页交换出去。具体的来说当内存基本用满的时候,系统会根据”vm.swappiness“这个参数来判断是把内存中很少用到的inactive内存交换出去,还是释放数据的cache。cache中缓存着从磁盘读出来的数据,根据程序的局部性原理,这些数据有可能在接下来又要被读取;inactive 内存顾名思义,就是那些被应用程序映射着,但是“长时间”不用的内存。
我们通常强烈建议这个值小于等于10,最好是设置为0。原因很简单,对数据库这种需要集中CPU资源、大内存、高I/O的程序而言,如果用SWAP分区代替内存,那数据库服务性能将是不可接受的,还不如直接被OOM kill(数据库进程通常占用最多内存,最容易被OOM kill)来的痛快(早死晚死都是死,还不如痛快的死,反正很快就能重生,嗯)。
处理方法:
1 2 3 4 5 6 7 8 9 |
# 临时调整内存分配优先级方法; $ echo 0 > /proc/sys/vm/swappiness # 永久调整内存分配优先级方法; $ cat /etc/sysctl.conf vm.swappiness = 0 # 生效; $ sysctl -p |
先介绍完这么多信息,大家肯定已经不耐烦了,我们就来看看现场并进行排查吧。
二、现场排查
首先,看下系统整体的状况,如下图.2所示
从上图能看出来什么呢,有几个关键信息:
- 系统负载不算高,最近的平均load是6.8;
- CPU负载也不算高,有大量的空闲,idle为 98.4%;
- 内存主要分配给mysqld进程,占用了80.2%;
- 尽管物理内存有256G,空闲的也将近39G,但确实发生swap了,并且把SWAP都耗尽了。
得到第一个排查结果:物理内存还有不少空闲,但却把swap都耗尽了。作为一个有经验的DBA,遇到这种情况第一反应是什么呢?嗯,先不点破,继续往下看。再执行free -gt 查看内存、SWAP消耗情况,如下图.3所示
看出来了吧,尤其是参加过 知数堂培训MySQL DBA优化班课程 的同学应该都知道,我们在课上多次强调:遇到这种情况,第一条件反射很直接就是:发生内存泄露(memory leak)了。
一般来说,如果发现内存统计结果中,cached 和 used 相差特别大的话,基本可确定系统发生内存泄露。相应的处理手法有:
- 治标的办法:择机重启进程,彻底释放内存归还给OS;
- 治本的办法:找到代码中导致泄露的代码,修复之(我们这次面对的是mysql代码,还是去官方提交bug吧,哈哈);
- 治本的办法:升级程序版本,通常新版本会解决旧版本存在的问题,推荐此方案。
再看下MySQL中内存相关选项怎么配置的:
除了 innodb-buffer-pool 分配的稍微多一些外,其他的还算正常。看了下,MySQL的版本是5.6.19,看来是有必要升级到5.6系列的最新版本。
到这里,我们得到第二个排查结果:mysqld进程发生内存泄露,建议择机重启进程,并尽快安排升级到最新版本。
然而,仅仅是因为mysqld进程内存泄露导致的SWAP吗,貌似不全然?还记得上面我们有个地方还没点破的不:物理内存还有不少空闲,但把swap都耗尽了。同样滴,这种案例在我们知数堂的MySQL DBA培训课程里也被多次谈及,绝大多数情况是因为没有关闭NUMA引起的。在运行数据库进程的服务器上,强烈建议关闭NUMA,在之前的分享 比较全面的MySQL优化参考(上篇) 中也有提及。我们接着来看下NUMA的状况:
从上面两张图可见,NUMA问题导致其中一个CPU可分配的内存远小于另一个(1.8G vs 38G),那么这个CPU上如果要申请大内存时,显然不够了,所以发生SWAP。关于NUMA的相关背景知识我这里不赘述。
因此,我们得到第三个排查结果:由于服务器硬件、系统设置不当,没有关闭NUMA,导致发生SWAP。建议方案有:
- 在BIOS设置层面关闭NUMA,缺点是需要重启OS;
- 或修改GRUB配置文件,缺点也是要重启OS;
- 升级MySQL版本到5.6.27及以后,新增了一个选项innodb_numa_interleave,只需要重启mysqld实例,无需重启OS,推荐此方案。
说到这里,这个问题已经基本分析清楚了,相关的解决建议也给了,根据自己的情况去评估选择哪个方案即可。
三、写在最后
类似的案例发生也不是一次两次了,我肯定以后还会继续存在,看完案例的的同学也没办法立刻把所有服务器上的NUMA策略全部修整过来,或者可能冲动一下想修复,但过几天就又给忘光了。
老叶曾多次在各种场合不厌其烦地强调一些基础的MySQL运维、开发规范,也正是因为看到了其实还有不少此类问题的存在。这些基础的规范都没执行到位的话,早晚是有一天要去填坑的,不管是填自己的挖下的坑,还是前人留下的坑,哈哈。
转载了imysql.com的一篇文章,主要是方便自己记忆和翻阅,另外是给这篇文章配一个案例:MongoDB&MySQL对于NUMA架构CPU的问题。