|作者邓英明,腾讯云DBA,擅长数据库架构设计、故障诊断、性能优化,现主要负责腾讯云数据库MySQL/TDSQL-C/Redis相关工作。在日常工作中,我们可能会时不时收到内存占用过高的告警,那么我们应该如何处理呢?本文将从Linux和MySQL两个层面介绍内存管理的相关知识点,希望能给大家带来一些帮助,以便更好的处理内存问题。一、如何理解内存指标如果遇到内存问题,可以先通过free、vmstat、top等命令查看。free命令可以获取系统内存的整体使用情况;vmstat命令可以实时观察内存的变化;top命令可以排序获取内存占用大的进程。这里简单介绍下free命令的输出(以CentOS7为例):totalusedfreesharedbuff/cacheavailableMem:8008704523487615792064026159082467292Swap:204702047第一行是内存数据1.total:总内存大小,对应MemTotal2。/proc/meminfo中使用的内存大小,对应/proc/meminfo(MemTotal-MemFree-Buffers-Cached-Slab)3。free:未使用的内存大小,对应/proc/meminfo的MemFree4。buff/cache:使用的缓存大小,Buffers+Cached对应/proc/meminfo5。available:可用内存大小,是一个估计值,/proc/meminfo的MemAvailable对应的第二行是swap分区数据1。total:swap分区的总大小,对应/proc/meminfo2的SwapTotal。used:使用过的swap分区,对应/proc/meminfo3的(SwapTotal-SwapFree)。free:未使用的内存大小,对应这里的/proc/meminfo的SwapFree值得注意的是,Linux操作系统会最大限度地使用内存,空闲内存较少,并不代表系统内存不够用。个人建议,一方面要观察内存增长的整体趋势是否在逐渐趋于稳定,以及used和buff/cache的变化;另一方面,需要观察swap分区是否被频繁使用。当然,这里应该避免使用NUMA和swapiness。设置不当引起的干扰。二、MySQL如何使用内存在MySQL中,内存占用主要包括以下几部分,全局共享内存、线程独占内存、内存分配器占用的内存,如下:全局共享1.innodb_buffer_pool_size:InnoDB缓冲池的大小2.innodb_additional_mem_pool_size:InnoDB存储数据字典等内部数据结构内存大小,5.7已去掉3.innodb_log_buffer_size:InnoDB日志缓冲区大小4.key_buffer_size:MyISAM缓存索引块内存大小5.query_cache_size:查询缓冲区大小8.0已去掉线程独占1.thread_stack:每个线程分配的栈大小2.sort_buffer_size:排序缓冲区大小3.join_buffer_size:连接缓冲区大小4.read_buffer_size:MyISAM顺序读缓冲区大小5.read_rnd_buffer_size:MyISAM随机大小读取缓冲区的大小和MRR缓冲区的大小6.tmp_table_size/max_heap_table_size:临时内存表的大小7.binlog_cache_size:二进制日志缓冲区的大小。系统申请分配;此外,大多数内存管理都需要通过内存分配器。为了实现更高效的内存管理,避免频繁的内存分配和回收,内存分配器会长期占用大量内存,供内部重用。关于内存分配器的选择,推荐使用jemalloc,可以有效解决内存碎片,提高整体性能。因此,MySQL内存占用高的原因可能包括:innodb_buffer_pool_size设置过大、连接数/并发数过高、大量排序操作、内存分配器占用、MySQLBug等。一般来说,在MySQL的整个运行周期中,刚启动时内存会上升得比较快,运行一段时间后会逐渐稳定下来。这种情况不需要过多关注;如果内存一直在增长,没有释放,那么我们需要进一步分析是什么原因造成的。3、谁占用了内存?大多数情况下,我们不需要花太多精力去关注MySQL的内存使用情况;但不排除内存占用确实存在异常。这个时候怎么办?深入调查如何?其实MySQL官方提供了一个强大的实时监控工具——performance_schema库下的监控内存表。通过这个工具,我们可以清楚的观察到MySQL内存是谁占用的,占用了多少。启用内存监控。在实例启动时启用它。我们可以选择在实例启动时开启内存监控收集器。具体方法如下:vimy.cnfperformance-schema-instrument='memory/%=ON'禁用方法如下:vimy.cnfperformance-schema-instrument='memory/%=OFF'我们也可以选择在实例运行时启用内存监控收集器。具体方法如下:mysql>UPDATEperformance_schema.setup_instrumentsSETENABLED='YES'WHERENAMELIKE'memory/%';禁用方法如下:mysql>UPDATEperformance_schema.setup_instrumentsSETENABLED='NO'WHERENAMELIKE'memory/%';因为收集器的实现原理是在分配/回收内存时更新对应内存监控表的数据;换句话说,即收集器只有在启用后才能监控内存使用情况;而MySQL有很大一部分内存是在实例启动时预先分配的,所以为了准确监控实例的内存使用情况,需要在实例启动时开启内存收集器。内存监控表在performance_schema库下,提供多个维度的内存监控表,如下:用户监控表memory_summary_global_by_event_name:全局纬度内存监控表内存监控表包括以下关键字段:COUNT_ALLOC:内存分配次数COUNT_FREE:内存回收次数SUM_NUMBER_OF_BYTES_ALLOC:内存分配大小SUM_NUMBER_OF_BYTES_FREE:内存回收大小CURRENT_COUNT_USED:当前分配的内存,按COUNT_ALCOUNT_FREE计算CURRENT_NUMBER_OF_BYTES_USED:当之前分配的内存大小,通过SUM_NUMBER_OF_BYTES_ALLOC-SUM_NUMBER_OF_BYTES_FREE计算得到LOW_COUNT_USED:CURRENT_COUNT_USED的最小值_GH_COUNT:CURRENT_COUNT_USED的最小值_GH_COUNT_COUNT_USED的最大值LOW_NUMBER_OF_BYTES_USED:CURRENT_NUMBER_OF_BYTES_USED的最小值HIGH_NUMBER_OF_BYTES_USED:CURRENT_NUMBER_OF_BYTES_USED的最大值接下来,让我们看一个正常运行Thememoryusageoftheinstanceisasfollows:mysql>selectUSER,HOST,EVENT_NAME,COUNT_ALLOC,COUNT_FREE,CURRENT_COUNT_USED,SUM_NUMBER_OF_BYTES_ALLOC,SUM_NUMBER_OF_BYTES_FREE,CURRENT_NUMBER_OF_BYTES_USEDfromperformance_schema.memory_by_account_by_event_nameorderbyCURRENT_NUMBER_OF_BYTES_USEDdesclimit10;+------+------------+----------------------------+------------+------------+----------------+---------------------------+-----------------------+----------------------------+|USER|HOST|EVENT_NAME|COUNT_ALLOC|COUNT_FREE|CURRENT_COUNT_USED|SUM_NUMBER_OF_BYTES_ALLOC|SUM_NUMBER_OF_BYTES_FREE|CURRENT_NUMBER_OF_BYTES_USED|+-----+---------+---------------------------+------------+------------+------------------+----------------------------+-------------------------+--------------------------------+|NULL|NULL|memory/innodb/buf_buf_pool|32|0|32|4500488192|0|4500488192||NULL|NULL|内存/innodb/os0event|1573559|0|1573559|214004024|0|214004024||NULL|NULL|内存/innodb/hash0hash|82|6|76|397976480|227067024|170909456||NULL|NULL|memory/innodb/log0log|10|0|10|33565840|0|33565840||root|localhost|memory/innodb/std|3650638|3043111|607527|160817813634|160817483636|19443168||NULL|NULL|内存ory/mysys/KEY_CACHE|3|0|3|8390768|0|8390768||NULL|NULL|memory/innodb/ut0pool|2|0|2|4194480|0|4194480||NULL|NULL|memory/innodb/sync0arr|3|0|3|2506184|0|2506184||NULL|NULL|memory/innodb/lock0lock|33|0|33|2245040|0|2245040||root|localhost|memory/innodb/mem0mem|9897784|9896793|991|8845389160|8843147749|2241411|+------+------------+---------------------------+------------+------------+------------------+----------------------------+---------------------------+----------------------------+10rowsinset(0.01sec)再看一遍Bug#86821的场景,bufferpool占用最大内存是正常的,但是存储过程占用3GB是不正常的,存在内存泄露的风险;可以看出,通过内存监控表,我们可以快速定位内存占用异常的问题mysql>selectevent_name,current_alloc,high_allocfrommemory_global_by_current_byteswherecurrent_count>0;+--------------------------------------------------------------------------+--------------+------------+|event_name|current_alloc|high_alloc|+----------------------------------------------------------------------------+----------------+------------+|内存/innodb/buf_buf_pool|7.29GiB|7.29GiB||内存/sql/sp_head::main_mem_root|3.21GiB|3.62GiB||内存/innodb/hash0hash|210.16MiB|323.63MiB||内存/sql/TABLE|183.82MiB|190.28MiB||内存/sql/Query_cache|128.02MiB|128.02MiB||memory/mysys/KEY_CACHE|64.00MiB|64.00MiB||memory/innodb/log0log|32.08MiB|32.08MiB||memory/innodb/parallel_doublewrite|30.27MiB|30.27MiB||内存/performance_schema/table_handles|27.19MiB|27.19MiB||memory/innodb/mem0mem|19.14MiB|20.79MiB||memory/performance_schema/events_statements_history_long|13.66MiB|13.66MiB||memory/performance_schema/events_statements_summary_by_digest.tokens|9.77MiB|9.77MiB|另外,如果我们在内存监控表中看到一些不熟悉的事件,可以阅读官方文档或者源码继续进一步解读,比如memory/innodb/os0event/**@fileinclude/os0event.hTheinterfacetotheoperatingsystemconditionvariablesCreated2012-09-23SunnyBains(splitfromos0sync.h)***********************************************************/memory/innodb/hash0hash/**@fileinclude/hash0hash.hThesimplehashtableutilityCreated5/20/1997HeikkiTuuri**************************************************************/四。总结一般来说,只要我们的操作系统/数据库配置比较合理(NUMA、swapiness、jemalloc、innodb_buffer_pool_size等),大多数情况下是不需要关注内存问题的;提供实时监控工具——内存监控表,可快速定位;但是需要注意的是,开启内存收集器也会带来一些问题,比如额外的内存占用和性能损失。一般建议是系统出现内存问题后,重启实例启用,等待再次出现
