MySQL内存不足故障分析与解决

一、故障现象:内存占用异常的服务器

某天,运维人员发现一台数据库服务器发出内存不足告警,监控显示空闲内存已低于 10%。通过top命令查看系统资源使用情况,输出如下:
top - 13:45:43 up 1835 days, 20:52,  2 users,  load average: 0.02, 0.03, 0.05
Tasks: 210 total,   1 running, 208 sleeping,   1 stopped,   0 zombie
%Cpu(s):  0.5 us,  0.6 sy,  0.0 ni, 98.9 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem : 32780028 total,   905684 free, 19957900 used, 11916444 buff/cache
KiB Swap:        0 total,        0 free,        0 used.  3448260 avail Mem

   PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND
     2677 mysql     20   0   20.1g  15.1g   3392 S   0.0 48.2 430:17.58 mysqld
    10549 polkitd   20   0 3277476   3.1g    632 S   0.3  9.9 146:47.24 redis-server
    18183 root      20   0  877308 215868   1892 T   2.7  0.7   2736:45 xxxxxx
     442 root      20   0  160244  93016  88552 S   0.3  0.3 314:14.86 systemd-journal
    32537 root      20   0  731620  58360  54588 S   0.3  0.2  29:09.61 rsyslogd
 

系统内存总量为 32GB,已使用 19GB,缓存和缓冲区占用 11GB,但可用内存仅剩约 3GB。进一步分析发现,占用内存最多的是 MySQL 和 Redis 进程,合计约 18.2GB,其他进程内存占用都比较低。那么问题来了:缓存和缓冲区的 11GB 内存中,为什么只有 3GB 是有效的,剩下的 8GB 去哪儿了?

二、深入分析:被隐藏文件占用的内存

1. 内存使用详情分析

执行free -m命令进一步查看内存使用情况:
 
              total        used        free      shared  buff/cache   available
Mem:          32011       19490         881        8762       11639        3366
Swap:             0           0           0
 

结果显示shared内存占用竟然高达 8GB。通过man命令查看帮助信息得知,shared内存来源于/proc/meminfo中的Shmem,主要由tmpfs使用。

2. tmpfs 内存占用定位

使用df -h命令查看文件系统使用情况:
Filesystem      Size  Used Avail Use% Mounted on
devtmpfs         16G     0   16G   0% dev
tmpfs            16G   16K   16G   1% dev/shm
tmpfs            16G  8.6G  7.1G  55% run
tmpfs            16G     0   16G   0% sys/fs/cgroup
 

发现/run目录占用了 8.6GB 内存,这与shared内存占用量一致。接下来分析/run目录下的内存占用情况:

du -am run|sort -rn -k1|head -10
8761    run
7137    run/systemd
7126    run/systemd/users
1624    run/log/journal/89308070e0c04c6a86bf577f4064efca
1624    run/log/journal
1624    run/log
 

内存主要消耗在/run/systemd/users/run/log/journal目录,分别占用 7126MB 和 1624MB。其中/run/systemd/users目录的内存占用异常高,进一步查看该目录下的文件:
 
ls -l run/systemd/users
total 44
-rw-r--r-- 1 root root 41056 Mar 23 14:14 0
 

乍一看,该目录下只有一个约 40KB 的文件,这与du统计的 7GB 差异巨大。难道有隐藏文件?使用find命令查找隐藏文件:
 
find run/systemd/users|less
/run/systemd/users/
/run/systmd/users/0/
/run/systemd/users/.#0kRUlqC/
/run/systemd/users/.#0Qxvu5J/
/run/systemd/users/.#03DvfrF/
......
find run/systemd/users|wc -l
337632
 

不查不知道,一查吓一跳!该目录下隐藏文件数量高达 30 多万个,最早的文件可以追溯到 2018 年,最新的文件则是当天产生的。随便打开一个隐藏文件查看内容:
 
less run/systemd/users/.#03DvfrF
# This is private data. Do not parse.
NAME=root
STATE=active
RUNTIME=/run/user/0
SLICE=user-0.slice
DISPLAY=4231719
REALTIME=1521010223727718
MONOTONIC=79029110787
SESSIONS=4232100 4232099 4232098 ......
 
文件内容显示这些是 root 用户的 session 信息。使用loginctl命令查看 session 信息:
 
loginctl list-sessions
   SESSION        UID USER             SEAT
         24597          0 root
        146401          0 root
        133160          0 root
         82494          0 root
         82514          0 root
        106049          0 root
   ......
loginctl list-sessions|awk '{print $3}'|sort|uniq -c
      1       1 listed.
   2131 root
      1 USER
 

结果显示 root 用户的 session 数竟然高达 2131 个!随便查看一个 session 的状态:
loginctl session-status 24597
24597 - root (0)
           Since: Tue 2018-03-27 08:35:01 CST; 4 years 11 months ago
          Leader: 25599
         Service: crond; type unspecified; class background
           State: active
            Unit: session-24597.scope
 

这些 session 大多是由crond产生的,且都处于active状态,但当前并没有分配相关进程。更令人惊讶的是,按 session 创建时间排序后发现,最近的 session 也是 2018 年产生的,也就是说这些 session 已经存在了将近 5 年!

三、问题根源:crond 产生的僵尸 session

进一步测试发现,当创建一个新的定时任务时,session 能正常分配进程,任务完成后 session 也能正常关闭。但不知为何,过去几年中产生的大量 crond session 没有被正确关闭,一直处于active状态,导致相关的临时文件保留在/run/systemd/users目录下,占用了大量内存。

通过lsof命令查看与这些 session 相关的文件描述符:
lsof -p `pidof dbus-daemon`|grep sessions|wc -l
2126
lsof -p `pidof dbus-daemon`|tail -5
dbus-daem 560 dbus 2139w     FIFO               0,18      0t0  416861417 /run/systemd/sessions/156582.ref
dbus-daem 560 dbus 2140w     FIFO               0,18      0t0  417383549 /run/systemd/sessions/156774.ref
dbus-daem 560 dbus 2141w     FIFO               0,18      0t0  417291412 /run/systemd/sessions/156740.ref
dbus-daem 560 dbus 2142w     FIFO               0,18      0t0  620267085 /run/systemd/sessions/242902.ref
dbus-daem 560 dbus 2143w     FIFO               0,18      0t0  621086290 /run/systemd/sessions/243335.ref
 

可以看到,dbus-daemon进程打开了大量与 session 相关的文件描述符,这些文件都是保存在tmpfs中的,因此会占用内存。

四、解决方案:释放内存的多种选择

方案 1:调整数据库内存配置(在线处理)

考虑到服务器上主要服务为 MySQL 和 Redis,其中 MySQL 作为从库使用,未承载业务读流量,Redis 近期也将迁移。虽然/run/systemd/users目录占用的内存还在缓慢增长,但 5 年才占用了 8GB,增长速度较慢。因此可以采取以下措施:

  1. 在线收缩 MySQL 的innodb_buffer_pool_size参数,释放一部分内存给操作系统。
  2. 等待 Redis 迁移完成后,重启服务器彻底释放内存。

方案 2:手动清理僵尸 session(不重启服务器)

如果服务器不允许重启,可以采取以下步骤清理僵尸 session:

  1. 通过lsof命令确认这些隐藏文件当前未被使用。
  2. /run/systemd/users目录下的文件迁移到其他磁盘目录,尝试释放内存。
  3. 使用loginctl kill-session ID命令强制关闭这些僵尸 session。

五、最终处理:选择最稳妥的方案

综合考虑各种因素,运维人员最终选择了方案 1 进行处理:

  1. 调整 MySQL 的innodb_buffer_pool_size参数,释放部分内存。
  2. 制定计划,在 Redis 迁移完成后重启服务器,彻底解决内存占用问题。

六、经验总结:系统维护中的细节不容忽视

  1. 定期检查系统内存使用情况:不要等到告警产生后才发现问题,应定期通过topfreedf等命令检查系统内存使用情况。
  2. 关注 tmpfs 文件系统:tmpfs使用内存作为存储,当发现shared内存占用异常时,应检查/run/dev/shmtmpfs挂载点。
  3. 及时清理僵尸进程和 session:定期检查系统中的僵尸进程和过期 session,避免资源浪费。
  4. 合理配置定时任务:确保定时任务执行完毕后,相关的 session 和进程能正确关闭。
  5. 制定合理的维护计划:对于长期运行的服务器,应制定定期重启计划,释放长期积累的资源占用。

通过这个案例,我们可以看到,即使是看似简单的内存不足问题,背后也可能隐藏着复杂的原因。在系统维护过程中,只有深入分析、步步排查,才能找到问题的根源并采取有效的解决方案。

posted on 2025-07-03 09:36  数据派  阅读(90)  评论(0)    收藏  举报