数据库服务器内存的高使用率一直困扰着,明明used内存不高,但是available可用的内存已经达到警戒线,很大一部分又被buff/cache占用,这其中究竟是什么关系?
1、free命令与/proc/meminfo的映射关系
操作系统中常用TOP或者free命令查看当前系统的内存使用情况,free命令的输出结果如下所示,包含Mem和Swap两部分,通常在数据库服务器中会关闭swap空间。
total used free shared buff/cache available
Mem: 16310248 3223164 8006500 12348 5080584 11680584
Swap: 0 0 0
其中内存计算公式:total = used + free + buff/cache,而available为估算值。
- total: 系统总物理内存
- free: 完全未被使用的内存
- used: 已被使用的内存。计算得出的值,used = total - free - buff/cache
- shared: 被多个进程共享的内存。其数据主要来自/proc/meminfo中的Shmem字段
- buff/cache: 内核用于I/O优化的缓冲区(Buffers)和页缓存(Page Cache)的总和
- available: 预估的可供应用程序启动而无需进行交换的内存量
/proc/meminfo文件内容示例如下(数值单位为kB):
MemTotal: 16310248 kB
MemFree: 8006500 kB
MemAvailable: 11680584 kB
Buffers: 347604 kB
Cached: 3685460 kB
SwapCached: 0 kB
...
Shmem: 12348 kB
SReclaimable: 210352 kB
...
free命令Mem行各列与/proc/meminfo指标的精确映射关系如下所示:
| free命令 | /proc/meminfo对应指标/计算公式 | 解释 |
|---|---|---|
| total | MemTotal | 系统的总物理内存大小。 |
| used | MemTotal - MemFree - Buffers - Cached | 被应用程序和部分不可回收的内核结构占用的内存。free命令本身也可以通过内部计算 total - free - buff/cache 得出此值。 |
| free | MemFree | 完全未被使用的物理内存。 |
| shared | Shmem | 进程间共享的内存总量。主要包括System V共享内存、POSIX共享内存以及通过tmpfs实现的共享匿名映射。 |
| buff/cache | Buffers + Cached + SReclaimable | 用于I/O优化的、可被内核回收的缓存总和 |
| available | MemAvailable | 估算的、可供新应用程序启动而不会导致系统交换(swapping)的内存量。衡量系统可用内存最重要的指标。 |
1.1 buff/cache内存占用分析
在free命令的输出中,有时候看到buff/cache列的数值非常高,这部分是哪一部分占用的?先来看下buff/cache部分的构成:buff/cache是Linux内核为了提升系统I/O性能而设计的一套核心缓存机制的总称,它由两部分组成:Buffer Cache(缓冲区缓存)和Page Cache(页缓存):
- Page Cache(页缓存):当应用程序读取文件时,内核会首先检查所需数据是否已存在于Page Cache中。如果命中,则直接从内存返回数据,避免了磁盘I/O操作。如果未命中,内核会从磁盘读取数据,并将其一份存入Page Cache,以备后续访问。同样,当应用程序写入文件时,数据也通常先被写入Page Cache,然后由内核在稍后的某个时刻异步地写回磁盘 。
- Buffer Cache (缓冲区缓存):主要用于缓存块设备(如磁盘)的元数据(如文件系统的目录、权限等),以及在写操作时聚合小的写请求,优化磁盘写入。
buff/cache的占用者是操作系统内核本身,其目的是服务于所有进程的文件I/O请求。如果buff/cache占用高,并不一定代表内存不足。可能有以下原因:
- I/O操作:只要系统上有频繁的文件读写操作,例如编译代码、运行数据库、启动应用程序、甚至简单的ls命令,都会导致相关文件数据和元数据被加载到buff/cache中。
- 内核的缓存策略:内核会主动利用所有可用的空闲内存进行缓存。一个长时间运行且I/O密集的服务,其buff/cache会随着时间的推移而增长,直到接近系统总内存的某个稳定水平。
- 内存的可回收性:buff/cache中的大部分内存都被认为是可回收的(Reclaimable)。当应用程序需要更多内存时,如果空闲内存不足,内核会自动启动内存回收机制,从buff/cache中"驱逐"掉最近最少使用的缓存页,将这部分内存释放出来分配给应用程序 。
通过以下命令可查看哪些文件被缓存:
lsof | grep -E "REG.*(path_inode=|/.*)" | awk '{print $9}' | sort | uniq -c | sort -rn | head -20
在数据库服务器中,redo、binlog和relaylog文件的读写会占用较多的page cache。由于redo文件是复用的,因此产生的page cache占用是有限的,而binlog的写入是增量的,随着时间推移会持续占用更多的page cache。
1.2 Page Cache的清理机制
Buff/cache支持手动清理的方式,但是不建议在生产环境这样操作。通过向/proc/sys/vm/drop_caches文件写入特定数值来完成手动清理。
echo 1 > /proc/sys/vm/drop_caches:清理Page Cache。
echo 2 > /proc/sys/vm/drop_caches:清理dentries和inodes(属于Slab的一部分)。
echo 3 > /proc/sys/vm/drop_caches:清理Page Cache、dentries和inodes。
执行此操作后,会立即看到free命令中buff/cache值大幅下降,free值相应上升。这样操作会带来一定的性能抖动,因为下一次对相同文件的访问得从磁盘中重新读取,系统需要重新建立缓存。
更多的是利用系统后台回收线程kswapd,它会周期性地检查内存水位。当可用内存下降到低水位线以下时,kswapd会被唤醒,开始在后台异步地扫描和回收不活跃的内存页,直到可用内存恢复到高水位线以上。在系统监控sar --B对应的指标pgscank和pgscand可以看到page cache回收的情况。
操作系统中为了衡量内存的使用情况,定义了三个内存阈值(watermark,也称为水位),分别是 watermark[min/low/high]:

上图基本揭示了几个水位的含义,当MemFree低于watermark[low]时,kswapd进行内存回收,直到空闲内存达到watermark[high]后停止回收。如果申请内存的速度太快,导致空闲内存降至watermark[min]后,内核就会进行direct reclaim(直接回收),用回收上来的空闲页满足内存申请,这样会阻塞应用程序。而watermark[min]的大小等于内核参数min_free_kbytes的值,其他几个水位的关系是:
watermark[low] = watermark[min]*5/4
watermark[high] = watermark[min]*3/2
内核参数vm.min_free_kbytes为强制Linux内核保留的最小空闲内存,在数据库服务器中一般为内存的5%左右。vm.watermark_scale_factor控制内存水位线(min,low,high)之间的间距,值越大,kswapd的活动范围越宽,回收行为可能更平缓但也更频繁。
1.3 Available可用内存的估算
MemAvailable不是一个简单的统计值,而是内核为估算在不触发磁盘I/O交换的情况下,系统还能为进程提供多少内存而计算出的一个估算值。简化后的估算公式如下:
MemAvailable ≈ MemFree - watermark[low] + (PageCache - min(PageCache/2, watermark[low]))
- watermark[low]由上述公式可计算得到
- PageCache主要是/proc/meminfo中的Active(file)与Inactive(file)之和,是文件页缓存
所以有时候free命令看到系统中used内存并不高,但是available可用内存并不高,实际上很大一部分是被buff/cache占用了。以8C32G的gaussdb数据库服务器为例:
#通过free命令查看如下:
total-32019200、used-8868864、free-3593280、shared-8972224、buff/cache-19557056、available-9466240
#OS层内核参数
vm.min_free_kbytes=1594560
watermark[low] = 1594560*5/4 = 193200
#/proc/meminfo中page_cache=10584832
#available可用内存估算如下
MemAvailable ≈ MemFree - watermark[low] + (PageCache - min(PageCache/2, watermark[low]))
3593280 -- 1993200 + (10584832 - 1993200) = 10191712
估算的结果与free看到的结果基本一致。
2、GoldenDB和GaussDB数据库shared内存差异
观察到在不同数据库服务器(MySQL系如GoldenDB和GaussDB)中使用free命令查看内存使用时,发现虽然都使用了数据库buffer缓存空间,但是free命令显示的used和shared大小却并不一样。比如GoldenDB中,服务器内存为64G,数据库进程内存占用36g(bufferpool设置为24G),通过free看到used为42G、shared部分为3G,而buff/cache部分为14G;在GaussDB数据库中,服务器内存为64g、数据库进程占用38G(shared_buffer设置为22G),free看到used为22G、shared为24,buff/cache部分为34G。两种数据库中为什么会有差异?
2.1 InnoDB缓冲池的内存机制
GoldenDB存储引擎为InnoDB,缓冲池的大小由innodb_buffer_pool_size参数控制。InnoDB在启动时会一次性分配缓冲池所需的内存。默认情况下,InnoDB会通过标准的malloc()库函数来申请内存。malloc在申请大块内存时,其底层通常会调用mmap系统调用,并使用MAP_PRIVATE|MAP_ANONYMOUS标志。因此在默认配置下默认配置下的InnoDB缓冲池是一块巨大的私有匿名内存,这部分内存将被内核归类为AnonPages。它属于MySQL进程的私有地址空间,不属于Shmem,也不会被计入free命令的shared列。
同时,为了避免数据库层和操作系统层的双重缓存,InnoDB提供了innodb_flush_method参数。生产环境设置为O_DIRECT(Direct I/O),InnoDB在读写其数据文件(.ibd文件)时,会通知内核绕过页缓存,直接在InnoDB缓冲池和物理磁盘之间进行数据传输。O_DIRECT只对数据文件生效,其他文件如二进制日志Binlog、Relaylog以及表结构定义文件等,仍然使用标准的缓冲I/O,因此它们的内容还是会被内核缓存。
再看GoldenDB数据库进程,服务器内存为64G、数据库进程占用36G(其中bufferpool为24G),free命令看到used-42G、shared-3G、buff/cache-14G。
- InnoDB缓冲池的24GB是通过malloc分配的私有匿名内存。它不属于buff/cache,计入used内存部分。
- 数据库进程总内存为36GB,减去缓冲池的24GB,剩下的12GB是MySQL进程的其他私有内存,包括但不限于:连接线程的私有内存(如 sort_buffer_size,join_buffer_size)、SQL缓存、日志缓冲区(innodb_log_buffer_size)、全局协调内存、代码段等。这12GB同样是私有内存,属于正常的内存使用部分。
- shared部分为3G,主要是内核部分的缓存占用且不可回收
- buff/cache为14G,主要是内核对文件系统元数据的缓存、系统二进制文件和库文件缓存、innodb自身非O_DIRECT文件(如日志文件)的页缓存等。这部分内存是可回收的。
对于InnoDB存储引擎,当数据库内部占用的内存减少,但是TOP命令查看到的内存实际占用的RES内存并没有随之减少,这一现象和数据库内存的管理机制有关,后续将专门分析。
2.2 GaussDB缓冲池的内存机制
GaussDB数据库中的共享缓存由参数shared_buffers控制,与innodb引擎不同的是,gaussdb继承了postgresql的特性,其shared_buffers 是通过System V共享内存或POSIX共享内存来分配的。数据库启动时,主进程会向操作系统申请一块巨大的共享内存段。之后,所有为客户端连接服务的后端工作进程都会附加到这块共享内存上,共同读写其中的数据。这样,在内核看来这一块shared_buffer是一块典型的共享内存。再通过free命令查看时,反映在shared列中,计入/proc/meminfo的Shmem字段。
再看GaussDB数据库进程,服务器内存为64G、数据库进程占用28G(其中shared_buffer为22G),free命令看到used-22G、shared-24G、buff/cache-34G。
- shared达到24G是因为shared_buffer部分计入进来,加上系统上其它共享内存占用;
- buff/cache达到34G,其中24G是Shmem的贡献,其它10G为内核对数据库文件、系统文件、库文件等进行的纯粹的页缓存。
- used为22G,由于部分shared_buffer内存作为buff/cache从总内存中减去了。因此,shared_buffers部分不会贡献到used,used部分为数据库进程的私有内存如prepare缓存、plancache缓存等以及操作系统自身占用的内存空间。实际上在分析数据库进程内存消耗的时候,是需要加上这部分shared内存。
3、总结
在日常运维监控中,管理员关心的是数据库内存使用情况、是否存在泄露不回收的问题、服务器中available可用内存还有多少。当弄清楚上面available可用内存的计算方式以后,能够判断出一个正常运行的数据库环境下,available可用内存是否合理。当出现可用内存不足时,是否需要扩内存,还是要适当的调整bufferpool参数配置。比如一个MySQL数据库原来按照40%total内存分配bufferpool,实际上在数据库运行一段时间后buff/cache会占用一部分、used空间也上去,那么可用的内存空间就不够了,此时bufferpool设置为30%更为合理些。而对于GaussDB数据库,由于max_process_memory内存(total*65%)和shared_buffers在初始化实例的时候自动计算的,这部分配置比例是偏高的,实际运行的时候更容易出现可用内存低于阈值的情况。另外,对服务器而言即使扩容内存,这部分内存也会增加到buff/cache部分,而不是在free部分,对内核而言,尽可能多的将可用的内存作为缓存使用,以提升性能,除非重启服务器再重新分配内存。不过内存扩容后,可用的内存大小是会重新计算的。
参考资料: