在Linux系统中部署MySQL服务,经常会遇到MySQL因为OOM(out of memory)的原因被操作系统Kill,导致数据库服务异常。要分析该问题,首先我们需要了解一下Linux内存分配的基本策略。

一、Linux内存分配策略

一般写C语言程序,我们习惯使用malloc动态的申请内存空间(Java由JVM负责内存管理),malloc函数会向操作系统申请一段连续的内存单元,然后返回这段空间的起始地址。如果malloc函数返回为null则表示系统没有可分配的内存空间。这是我们的一般思维,当然这在某些操作系统中确实也是正确的(Solaris)。

但是Linux不是这样的,Linux的内存分配采取的是一种更加积极的分配策略,它假设应用申请了内存空间后并不会立即去使用它,所以允许一定量的超售,当应用真的需要使用它的时候,操作系统可能已经通过回收了其他应用的内存空间而变得有能力去满足这个应用的需求,简单的说,就是允许应用申请比实际可分配空间(包括物理内存和Swap)更多的内存,这个特性称为OverCommit。

这个特性在Linux操作系统里面也是可配的,可以通过设置/proc/sys/overcommit_memory为不同的值来调整OverCommit策略。overcommit_memory可以取3个值:

0:默认值,由Linux内核通过一些启发式算法来决定是否超售和超售的大小,一般允许轻微的超售,拒绝一些明显不可能提供的请求,同时做一些规则限制,比如不同用户overcommit的大小也不一样。

1:允许,不做限制的超售,当然这个也不是无限大,还受到寻址空间的限制,32位系统最大可能只有4G,64位系统大概16T左右。

2:禁止,禁止超售,系统能够分配的内存不会超过swap+实际物理内存*overcommit_ratio,该值可以通过/proc/sys/vm/overcommit_ratio设置,默认50%。

我们在一个1G内存,400M Swap的虚拟机上进行一个小测试,实验一是一个程序不停通过malloc申请空间,实验二是程序在malloc后,即用1填充该空间,对比两个程序申请到的空间大小。

图1

图2

我们观测到实验二(图2)申请的空间与实际物理空间大体一致,而实验一(图1)却申请到远远大于实际物理空间的虚拟空间,这也验证了Linux的超售的内存分配策略。本身这是一个系统的优化,无可厚非。但是我们知道,但凡“超售”都是基于不会有大量程序同时使用资源的假设,这显然也是有风险的。所以Linux又使用了一种OOM Killer(Out Of Memory killer)的机制,在系统可用内存(包括Swap)即将使用完之前,选择性的Kill掉一些进程以求释放一些内存。

二、Linux OOM Killer工作原理

这里就涉及到一个问题,到底Kill掉谁呢?一般稍微了解一些Linux内核的同学第一反应是谁用的最多,就Kill掉谁。这当然是Linux内核首先考虑的一种重要因素,但是也不完全是这样的,我们查一些Linux的内核方面的资料,可以知道其实Kill谁是由/proc//oom_score来决定的,这个值每个进程一个,是由Linux内核的oom_badness()函数负责计算的。那下面我们来仔细读一读badness()函数。

在badness()函数的注释部分,写明了badness()函数的处理思路:

1) we lose the minimum amount of work done

2) we recover a large amount of memory

3) we don't kill anything innocent of eating tons of memory

4) we want to kill the minimum amount of processes (one)

5) we try to kill the process the user expects us to kill, this  algorithm has been meticulously tuned to meet the principle of least surprise ... (be careful when you change it)

总的来说就是Kill掉最小数量的进程来获取最大数量的内存,这与我们Kill掉占用内存最大的进程是吻合的。

/*

* The memory size of the process is the basis for the badness.

*/

points = p->mm->total_vm;

分数的起始是进程实际使用的RAM内存,注意这里不包括SWAP,即OOM Killer只会与进程实际的物理内存有关,与Swap是没有关系的,并且我们可以看到,进程实际使用的物理内存越多,分数就越高,分数越高就越容易被牺牲掉。

/*

* Processes which fork a lot of child processes are likely

* a good choice. We add the vmsize of the childs if they

* have an own mm. This prevents forking servers to flood the

* machine with an endless amount of childs

*/

...

if (chld->mm != p->mm && chld->mm)

points += chld->mm->total_vm;

这段表示子进程占用的内存都会计算到父进程上。

s = int_sqrt(cpu_time);

if (s)

points /= s;

s = int_sqrt(int_sqrt(run_time));

if (s)

points /= s;

这表明进程占用的CPU时间越长或者进程运行的时间越长,分数越低,越不容易被Kill掉。

/*

* Niced processes are most likely less important, so double

* their badness points.

*/

if (task_nice(p) > 0)

points *= 2;

如果进程优先级低(nice值,正值低优先级,负值高优先级),则Point翻倍。

/*

* Superuser processes are usually more important, so we make it

* less likely that we kill those.

*/

if (cap_t(p->cap_effective) & CAP_TO_MASK(CAP_SYS_ADMIN) ||

p->uid == 0 || p->euid == 0)

points /= 4;

super用户的进程优先级较低。

/*

* We don't want to kill a process with direct hardware access.

* Not only could that mess up the hardware, but usually users

* tend to only have this flag set on applications they think

* of as important.

*/

if (cap_t(p->cap_effective) & CAP_TO_MASK(CAP_SYS_RAWIO))

points /= 4;

直接可以访问原始设备的进程优先级较高.

/*

* Adjust the score by oomkilladj.

*/

if (p->oomkilladj) {

if (p->oomkilladj > 0)

points <<= p->oomkilladj;

else

points >>= -(p->oomkilladj);

}

每个进程有个oomkilladj可以设置该进程被kill的优先级,这个参数看上去对Point影响还是比较大的,oomkilladj最大+15,最小是-17,越大越容易被干掉,这个值由于是移位运算,所以影响还是比较大的。

三、解决MySQL OOM的最佳实践

前面两章,我们分析了Linux内存分配的策略以及Linux通过使用OOM_Killer的机制解决了“超售”引起的风险,MySQL同其他的应用程序一样,在操作系统允许的范围内也是可以超售的,一般人理解,Innodb_buffer_pool必须小于实际物理内存,否则MySQL会启动失败。其实这是一个误区,这个不是MySQL层控制的,这个是操作系统(OS)层控制的,就是前面提到的/proc/sys/overcommit_memory控制OS是否允许“超售”。如果允许“超售”,则Innodb_buffer_pool可以远远超过实际的内存空间大小,但是这部分空间是没有使用的。

从上面的分析可以得出,MySQL OOM的起因是内存不足,内存不足主要有两个方面原因:第一是MySQL自身内存的规划。第二是一般部署MySQL的服务器,都会部署很多的监控或者定时任务脚本,而这些脚本往往缺少必要的内存限制,导致在高峰期的时候占用大量的内存,导致触发Linux OOM_Killer机制,MySQL由于持有的内存最多往往成为Kill对象。

那如何才能让MySQL摆脱被Kill的厄运呢?MySQL被Kill的根源在于Linux超售的内存分配机制,前面也提到了,只要存在这种超售的机制,就不可能完全避免某一个应用程序被Kill的风险。那要使得MySQL一定不会被Kill掉,只能禁止操作系统超出实际内存空间的分配内存。但是前面我们也提过,对于部署了MySQL的服务器,我们不建议这么做,因为MySQL的很多内存都是刚开始申请了,并不是立即使用的,OS一旦禁止超售,这不仅对MySQL自身内存规划提出更苛刻的要求,同时也存在内存无法充分利用的问题。同时,MySQL的每个连接的私有内存是动态分配的,如果分配不到,就会直接导致服务器Crash,这样也会增加MySQL Crash的风险。既然受限于操作系统,无法完全做到避免被Kill,那只能尽量降低MySQL被Kill的几率。我觉得至少可以做下面3个事情:

合理的规划MySQL的内存使用。

调整OOM_adj参数,将MySQL被OOM_Killer锁定的优先级降低。

加强内存的监控和报警,一旦报警,DBA应该迅速介入,Kill掉一些占用较多内存的连接。

四、MySQL参数优化

我们首先需要了解一下MySQL是如何使用内存的。MySQL使用的内存主要有2类,一类是共享内存,一类是连接私有内存。共享内存相关参数:

innodb_buffer_pool_size:使用过Innodb的同学都知道,这块内存是Innodb存储引擎最重要的内存,直接关系到MySQL的读写性能。与Myisam表只缓存索引,数据寄望于OS系统缓存不同,Innodb一般都会关闭OS的缓存,所有读到数据页和索引都直接存在数据库层的innodb_buffer_pool中的,这与Innodb是事务存储引擎有关,所以这个值越大MySQL性能当然也越好,但是有个前提需要讲明白,innodb_buffer_pool越大,不仅预热慢,崩溃恢复的速度也会很慢。这个值一般在保证了其他用途的内存够用的前提下,应尽可能的大。我们来看一下AWS(亚马逊云计算)不同规格的RDS该参数的设置情况:

规格

内存

innodb_buffer_pool_size

比例

db.t1.micro

613M

311M

50%

db.m1.small

1.7G

1.1G

64%

db.m1.medium

3.75G

2.66G

70%

db.m1.large

7.5G

5.47G

72%

db.m1.xlarge

15G

11G

73%

db.m2.xlarge

17.1G

12G

70%

db.m2.2xlarge

34G

25G

73%

db.m2.4xlarge

68G

51G

75%

之所以AWS的各种规格的内存这么零碎,我觉得可能AWS已经抛掉了一些它自己部署的一些程序持有的内存,所以这里就已经是MySQL实际能够使用的内存大小了,基本上规格越低,Innodb_buffer_pool_size的比例越小,这个主要是因为每个连接的私有内存是固定的,所以对于小规格的实例,本身内存就比较小,为了保证一定的并发连接数,所以Innodb_buffer_pool_size也相对较小,这个是合理的。我们目前RDS一刀切75%的Innodb_buffer_pool分配显然是不合理,应该按照实际内存大小,内存越小,比例越低的原则进行设定。

innodb_additional_mem_pool_size:主要用于存放MySQL内部的数据结构和Innodb的数据字典,所以大小主要与表的数量有关,表越多值越大。庆幸的是这个值是可变的,如果不够用的话,MySQL会向操作系统申请的。该值默认8M,AWS所有规格都是统一的2M,由于这个值可以动态申请,所以我觉得2M应该是满足需求的。

innodb_log_buffer_size:这个是redolog的缓冲区,为了提高性能,MySQL每次写日志都将日志先写到一个内存Buffer中,然后将Buffer按照innodb_flush_log_at_trx_commit的配置刷到disk上。目前,我们所有实例的innodb_flush_log_at_trx_commit设置为了1,即每次事务提交都会刷新Buffer到磁盘,保证已经提交的事务,redo是不会丢的。AWS该值也设的是1(为了保证不丢数据),这个值的大小主要影响到刷磁盘的次数,设置的过小,Buffer容易满,就会增加fsync的次数,设置过大,占用内存。该值默认是8M,AWS所有规格统一128M,我觉得目前每次提交都会刷buffer,所以除非有大事务的情况,一般buffer不太可能被占满,所以没必要开的很大,8M应该是满足需求的。

key_buffer_size:myisam表的键缓存,这个只对myisam存储引擎有效,所以对于我们绝大多数使用Innodb的应用,无需关心。

query_cache_size:MySQL对于查询的结果会进行缓存来节省解析SQL、执行SQL的花销,query_cache是按照SQL语句的Hash值进行缓存的,同时SQL语句涉及的表发生更新,该缓存就会失效,所以这个缓存对于特定的读多更新少的库比较有用,对于绝大多数更新较多的库可能不是很适用,比较受限于应用场景,所以AWS也把这个缓存给关了。我觉得这个值默认应该关闭,根据需求调整。

上面这些就是MySQL主要的共享内存空间,这些空间是在MySQL启动时就分配的,但是并不是立即使用的。MySQL还有一部分内存是在用户连接请求到达时动态分配的,即每个MySQL连接都单独一个缓存,这部分缓存主要包括:

read_buffer_size & read_rnd_buffer_size这两个值主要针对的都是Myisam表引擎,用来加快随机和顺序扫描表,对于innodb表,虽然在排序的时候,可以缓存临时文件中的索引,缓存嵌套查询的结果等作用,但是重要性已经下降很多了。这两个值前者默认128K,AWS统一256K,我们采用默认值。后者默认256K,AWS统一512K,我们也采用默认值。

sort_buffer_size:每一个要做排序的请求,都会分到一个sort_buffer_size大的缓存,用于做order by和group by的排序,如果设置的缓存大小无法满足需要,MySQL会将数据写入磁盘来完成排序。因为磁盘操作和内存操作不在一个数量级,所以sort_buffer_size对排序的性能影响很大。由于这部分缓存是即使不用这么大,也会全部分配的,所以对系统内存分配开销是比较大的,如果是希望扩大的话,建议在会话层设置,默认值2M,AWS也是2M。

thread_stack:默认256K,AWS256K,MySQL为每个线程分配的堆栈。

join_buffer_size:*每个连接的每次join都分配一个,默认值128K,AWS 128K;

binlog_cache_size:类似于innodb_log_buffer_size缓存事务日志,binlog_cache_size缓存Binlog,不同的是这个是每个线程单独一个,主要对于大事务有较大性能提升。默认32K,AWS 32K。

tmp_table_size:默认16M,用户内存临时表的最大值,如果临时表超过该值,MySQL就会把临时表转换为一个磁盘上mysiam表。如果用户需要做一些大表的groupby的操作,可能需要较大的该值,由于是与连接相关的,同样建议在会话层设置。AWS 16M

本文来自网易实践者社区,经作者郭忆授权发布。

mysql sys库 oom_MySQL OOM解决方案相关推荐

  1. mysql sys库 oom_MySQL 5.6因为OOM导致数据库重启

    MySQL 5.6因为OOM导致数据库重启 发布时间:2020-08-09 08:29:53 来源:ITPUB博客 阅读:89 作者:feelpurple 线上的一套MySQL 5.6的从库,因为OO ...

  2. MySQL默认数据库之sys库

    MySQL默认数据库之sys库 内容参考链接如下: https://dev.mysql.com/doc/refman/5.7/en/sys-schema-progress-reporting.html ...

  3. 漫谈MySQL六-系统数据库sys库详解

    目录 1.sys 介绍 2.sys 系统库使用 2.1.查看慢 SQL 语句慢在哪里 2.2.查询表的增.删.改.查数据量和 I/O 耗时统计 3.sys小结 大家好,我是王老狮,平时我们在使用mys ...

  4. mysql.sys用户权限_MySQL用户及权限小结

    备注:测试数据库版本为MySQL 8.0 这个blog我们来聊聊MySQL用户及权限 一.MySQL用户和权限介绍 MySQL权限系统通过下面两个阶段进行认证: 1.对连接的用户进行身份认证,合法的用 ...

  5. 浅谈mysql主从复制的高可用解决方案

    1.熟悉几个组件(部分摘自网络) 1.1.drbd      -- DRBD(Distributed Replicated Block Device),DRBD号称是 "网络 RAID&qu ...

  6. 微信表情符号 mysql_Emoji表情符号入MySQL数据库报错的解决方案

    Emoji表情符号入MySQL数据库报错的解决方案 发布时间:2020-08-15 08:21:52 来源:ITPUB博客 阅读:136 作者:bestpaydata auther:Jane.Hoo ...

  7. MySQL数据库常见错误及解决方案

    MySQL数据库常见错误及解决方案 1 MySQL无法重启问题解决Warning: World-writable config file '/etc/my.cnf' is ignored 原因 今天帮 ...

  8. oracle查询基表阻塞,在Oracle专家眼中,MySQL sys Schema是怎样一种存在?

    sys Schema的初衷 MySQL的数据字典经历了几个阶段的演进,MySQL4.1 提供了information_schema 数据字典,一些基础元数据可以通过SQL来查询得到. MySQL5.5 ...

  9. 易语言外部数据库MySQL支持库Access数据库Sqlite数据应用

    语言虽然有自己的数据库, 但易数据库只是以简单的.类似表格的形式组织数据信息,是一个小型数据库管理系统.有时编程中必须使用其它数据库.易语言支持以ODBC方式和ADO方式操作外部数据库. 一.ODBC ...

最新文章

  1. 主题图标_Avada主题网站favicon图标设置详细图文教程
  2. 武汉轻工大学计算机学院操作系统复试题目,2016年武汉轻工大学数学与计算机学院计算机组成原理复试考研复试题库...
  3. vue.js源码学习分享(一)
  4. Swagger 2——@ApiOperation注解、@ApiModel注解、@ApiImplicitParams注解、@ApiImplicitParam注解无效解决方案
  5. Windows Azure 社区新闻综述(#70 版)
  6. linux该专接本还是工作_是该专接本还是直接工作?学历和经验哪个重要?
  7. [ECMAScript] es6对函数做了哪些优化?
  8. 编程书说的“Go程序员应该让聚合类型的零值也具有意义”是在讲什么
  9. 51nod 1478 括号序列最长合法子段
  10. rpm安装mysql指定数据仓库_linux(center OS7)安装JDK、tomcat、mysql 搭建java web项目运行环境-Go语言中文社区...
  11. EmmyLua Unity断点调试
  12. m725扫描仪无法找到计算机,惠普m725扫描设置指定的网络文件夹路径不存在?
  13. Java递归法解决猴子吃桃问题_C语言实现猴子吃桃问题(循环、递归两种方法)...
  14. html在复选框内打钩,Selenium Webdriver Python如何在所有复选框中打勾
  15. 程序员高手和菜鸟的区别是什么?
  16. iOS 设备的屏幕尺寸、分辨率及其屏幕边长比例详细情况
  17. mc服务器常用指令_MC玩家必看常用指令大全
  18. 这份春招攻略,希望大家可以收藏!
  19. 广东省重点农业龙头企业补贴奖励金及申报条件,补贴50万
  20. EXCEL技巧——EXCEL如何实现隔行隔列求和

热门文章

  1. java毕业设计青少年心理健康公益扶助网站源码+lw文档+mybatis+系统+mysql数据库+调试
  2. 在线安装计算机应用软件,计算机应用与网络基础教程 常用软件安装new.ppt
  3. xgboost的使用简析
  4. 腾讯QQ2005Beta3珊瑚虫版v3.2发布(转)
  5. RabbitMQ 安装与web后台管理界面开启
  6. [机缘参悟-82]:企业、HR、管理者激励员工的本质
  7. 基于Java的Minecraft游戏后端自定义插件 的Java实践项目整理
  8. 2019年伯克利大学 CS294-112《深度强化学习》第1讲:课程介绍和概览(笔记)
  9. 2022.1.17学习总结
  10. JAVA算法:给定两个整数 n 和 k,返回 1 ... n 中所有可能的 k 个数的组合(JAVA)