mysql 5.7 内存使用率过高

经常在项目上碰到在没有大并发活跃SQL的情况下,为啥还会出现 MySQL 实际占用物理内存比 InnoDB_Buffer_Pool 的配置高很多而且不释放的现象?起初是怀疑被 performance_schema 吃掉了或是 MySQL 存在内存泄露,熟悉了 MySQL 和 Linux 的内存管理之后发现并非如此。

故障上报

  • 收到db服务器内存告警之后,登录服务器,执行 free -m 看到该机器 32G 内存,只剩下 516M 可用,使用 top 看到 mysqld 内存使用率 77.6%(约24.8G)
[root@221.115 ~]# free -m
              total        used        free      shared  buff/cache   available
Mem:          32115       30797         595         402         722         516
Swap:             0           0           0

[root@221.115 ~]# top
top - 09:55:28 up 377 days, 20:01,  1 user,  load average: 0.19, 0.16, 0.17
Tasks: 153 total,   1 running,  82 sleeping,   0 stopped,   0 zombie

KiB Mem : 32886604 total,   609688 free, 31536764 used,   740152 buff/cache
KiB Swap:        0 total,        0 free,        0 used.   529060 avail Mem 

   PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND
 13334 mysql     20   0   30.4g  24.3g      0 S   1.7 77.6 156566:00 mysqld
373603 mongodb   20   0 7190520   5.3g   2088 S   0.3 16.7   1430:26 mongod
  1807 root      20   0 1400668  29628   4144 S   0.7  0.1 732:10.61 telescope
   833 root      20   0   11.2g 233696      0 S   0.3  0.7 296:26.97 java
  • 查看 my.cnf 看到 innodb_buffer_pool_size 配置为 16G
[root@221.115 ~]# cat /etc/my.cnf | grep innodb_buffer_pool_size
innodb_buffer_pool_size = 16777216K
  • 登入 mysql 确认 @@innodb_buffer_pool_size 确实为 16G,且连接数只有 22,那么剩下的 8.8G(24.8-16)内存用到哪里了呢?
(root@localhost) [(none)]> SELECT @@version,@@innodb_buffer_pool_size;
+---------------+---------------------------+
| @@version     | @@innodb_buffer_pool_size |
+---------------+---------------------------+
| 5.7.34-37-log |               17179869184 |
+---------------+---------------------------+

(root@localhost) [(none)]> select count(*) from information_schema.processlist;
+----------+
| count(*) |
+----------+
|       22 |
+----------+

(root@localhost) [(none)]> show full processlist;
+----------+-----------------+-----------------------+-----------------+---------+----------+------------------------+-----------------------+-----------+---------------+
| Id       | User            | Host                  | db              | Command | Time     | State                  | Info                  | Rows_sent | Rows_examined |
+----------+-----------------+-----------------------+-----------------+---------+----------+------------------------+-----------------------+-----------+---------------+
|        1 | event_scheduler | localhost             | NULL            | Daemon  | 52617438 | Waiting on empty queue | NULL                  |         0 |             0 |
| 64804101 | user_xxljob     | 192.168.221.111:26551 | xxljob_alpha    | Sleep   |        0 |                        | NULL                  |         0 |             0 |
| 64804107 | user_xxljob     | 192.168.221.111:26553 | xxljob_alpha    | Sleep   |        0 |                        | NULL                  |         0 |           163 |
| 64804116 | user_xxljob     | 192.168.221.111:26555 | xxljob_alpha    | Sleep   |       13 |                        | NULL                  |         0 |             1 |
| 64804126 | user_xxljob     | 192.168.221.111:26557 | xxljob_alpha    | Sleep   |      215 |                        | NULL                  |         1 |             0 |
| 64804200 | user_xxljob     | 192.168.221.111:26559 | xxljob_alpha    | Sleep   |      126 |                        | NULL                  |         1 |             0 |
| 64804212 | user_xxljob     | 192.168.221.111:26561 | xxljob_alpha    | Sleep   |      110 |                        | NULL                  |         1 |             0 |
| 64804229 | user_xxljob     | 192.168.221.111:26563 | xxljob_alpha    | Sleep   |       87 |                        | NULL                  |         1 |             0 |
| 64804246 | user_xxljob     | 192.168.221.111:26565 | xxljob_alpha    | Sleep   |       69 |                        | NULL                  |         1 |             0 |
| 64804259 | root            | localhost             | NULL            | Query   |        0 | starting               | show full processlist |         0 |             0 |
| 64804276 | user_idspapidoc | 192.168.221.138:64761 | idspapidocument | Sleep   |       25 |                        | NULL                  |         1 |             0 |
| 64804279 | user_idspapidoc | 192.168.221.138:64763 | idspapidocument | Sleep   |       24 |                        | NULL                  |         1 |             0 |
| 64804280 | user_xxljob     | 192.168.221.111:26567 | xxljob_alpha    | Sleep   |       24 |                        | NULL                  |         1 |             0 |
| 64804281 | user_idspapidoc | 192.168.221.138:64765 | idspapidocument | Sleep   |       23 |                        | NULL                  |         1 |             0 |
| 64804282 | user_idspapidoc | 192.168.221.138:64767 | idspapidocument | Sleep   |       22 |                        | NULL                  |         1 |             0 |
| 64804283 | user_xxljob     | 192.168.221.111:26569 | xxljob_alpha    | Sleep   |       21 |                        | NULL                  |         1 |             0 |
| 64804286 | user_idspapidoc | 192.168.221.138:64769 | idspapidocument | Sleep   |       19 |                        | NULL                  |         1 |             0 |
| 64804287 | user_idspapidoc | 192.168.221.138:64771 | idspapidocument | Sleep   |       15 |                        | NULL                  |         1 |             0 |
| 64804296 | user_idspapidoc | 192.168.221.138:64773 | idspapidocument | Sleep   |       12 |                        | NULL                  |         1 |             0 |
| 64804297 | user_idspapidoc | 192.168.221.138:64775 | idspapidocument | Sleep   |       11 |                        | NULL                  |         1 |             0 |
| 64804298 | user_idspapidoc | 192.168.221.138:64777 | idspapidocument | Sleep   |       10 |                        | NULL                  |         1 |             0 |
| 64804301 | user_idspapidoc | 192.168.221.138:64779 | idspapidocument | Sleep   |        6 |                        | NULL                  |         1 |             0 |
+----------+-----------------+-----------------------+-----------------+---------+----------+------------------------+-----------------------+-----------+---------------+
22 rows in set (0.00 sec)

知识储备

  • MySQL 的内存占用主要由两部分组成,global_buffers(全局共享缓存,创建实例即分配)与 all_thread_buffers(所有线程独立缓存),如下图所示:

  • 如上图示,global_buffers = Sharing + InnoDB_Buffer_Pool
  • 如上图示,all_thread_buffers = 当前活跃连接数 * (Thread memory)
  • 其中 InnoDB_Buffer_Pool 是 MySQL 中内存占用中最大的一块,为常驻内存,除非 MySQL 进程退出才会释放。
  • 而另外一块比较吃内存的就是线程缓存。例如常见的 join_buffer、sort_buffer、read_buffer 等,通常与连接数成正比。即连接数越高,并发越高,线程缓存占用总量就越高,但是这类缓存往往会随着连接关闭而释放,并非常驻内存。一些特殊的 SQL 或字段类型会导致单个线程可能分配多次缓存,因此当出现 OOM 异常,都是由各个连接的私有内存造成的,通过限制数据库的连接数和优化低效 SQL,可降低内存利用率过高的风险
  • 开启 performance_schema 的内存监控功能,查询 memory_summary 开头的表来分析内存使用情况
#.mysql总内存
SELECT (@@key_buffer_size + @@tmp_table_size + @@innodb_buffer_pool_size + @@innodb_log_buffer_size + 200 * (@@read_buffer_size + @@read_rnd_buffer_size + @@sort_buffer_size+ @@join_buffer_size + @@binlog_cache_size + @@thread_stack))/1024/1024/1024 AS total_memory;

#.global 共享内存分配情况
show variables where variable_name in ('innodb_buffer_pool_size','innodb_log_buffer_size','innodb_additional_mem_pool_size','key_buffer_size','query_cache_size');

#.session 私有内存分配情况
show variables where variable_name in ('read_buffer_size','read_rnd_buffer_size','sort_buffer_size','join_buffer_size','binlog_cache_size','tmp_table_size');

#.找到MySQL里到底谁消耗了更多内存
select event_name,SUM_NUMBER_OF_BYTES_ALLOC 
from performance_schema.memory_summary_global_by_event_name order by SUM_NUMBER_OF_BYTES_ALLOC desc LIMIT 20;

#.查看都有哪些内部线程消耗了更多内存
select THREAD_ID, EVENT_NAME, COUNT_ALLOC, SUM_NUMBER_OF_BYTES_ALLOC/1024/1024, CURRENT_NUMBER_OF_BYTES_USED/1024/1024 
from performance_schema.memory_summary_by_thread_by_event_name order by CURRENT_NUMBER_OF_BYTES_USED desc limit 20;

#.查看thread pool相关设置
show variables like 'thread%';

select thread_id, user, current_allocated, total_allocated from sys.memory_by_thread_by_current_bytes limit 20;

发生原因

  • 低效的 SQL 语句或数据库参数设置不当都可能会导致内存利用率升高
  • mysql 5.7 的一个已知问题(bug#91710),只能重启mysql服务以释放内存

解决方案

  • 执行 show full processlist; 看看有没有什么异常的进程,比如:长时间的进程,或者 Binlog Dump
  • 优化慢 SQL,减少 session 级的私有内存使用量
  • 减少无效的长连接,在不影响业务的情况下,降低程序侧的连接池配置或者降低程序侧的并发度
  • 针对 mysql 5.7 建议 采用Jemalloc实现mysql服务端内存回收

附1:推荐 jemalloc 管理内存

  • 推荐用 Jemalloc 替代glibc库的malloc内存管理方式,它可以使内存快速释放给操作系统,减少系统内存使用过高而被OOM,适合多线程下内存分配管理,它与 Google TCMalloc 皆为内存管理器领域最高水平。更多关于malloc的信息,请参考 --malloc-lib=[lib_name]

如何确认mysqld已加载jemalloc

  • 执行如下命令若有返回,则代表 mysqld 已加载了 jemalloc
[root@localhost ~]# yum install -y -q lsof
[root@localhost ~]# lsof -p `pidof mysqld` | grep -i jemalloc
mysqld  13334 mysql  mem  REG  8,1  1929280  939276 /opt/mysql/lib/mysql/libjemalloc.so.1

如何配置mysqld加载jemalloc

  • 编译安装 jemalloc
cd /opt/
wget -c http://iso.sqlfans.cn/linux/jemalloc-5.3.0.tar.bz2
# yum install -y -q bzip2
tar jxvf jemalloc-5.3.0.tar.bz2
cd jemalloc-5.3.0
./configure --prefix=/usr/local/jemalloc
make -j 4 && make install
  • 找到 jemalloc 动态库的位置,修改 mysqld_safe,通过指定 LD_PRELOAD 环境变量加载 jemalloc
[root@localhost ~]# find / -name libjemalloc.so
/usr/local/jemalloc/lib/libjemalloc.so

[root@localhost ~]# grep -A 2 executing /opt/mysql/bin/mysqld_safe
# executing mysqld_safe
export LD_PRELOAD=/usr/local/jemalloc/lib/libjemalloc.so
  • 重启mysql服务
mysqladmin -udba_admin -pcf_rB1NKCzbaQuPH -S /tmp/mysql_3306.sock shutdown
mysqld_safe --defaults-file=/data/mysql_3306/my_3306.cnf &
  • 确认 mysqld 已加载了 jemalloc
[root@localhost ~]# lsof -p `pidof mysqld` | grep -i jemalloc
mysqld  30531 mysql  mem  REG  253,1  5960136  1064343  /usr/local/jemalloc/lib/libjemalloc.so.2

[root@localhost ~]# ll /usr/local/jemalloc/lib/libjemalloc.so*
lrwxrwxrwx 1 root root      16 Mar 19 15:27 /usr/local/jemalloc/lib/libjemalloc.so -> libjemalloc.so.2
-rwxr-xr-x 1 root root 5960136 Mar 19 15:27 /usr/local/jemalloc/lib/libjemalloc.so.2

附2:注意 FEDERATED 引擎

  • 注意:FEDERATED 引擎的内存使用不受 innodb_buffer_pool_size 的限制
  • 示例:如下 aaa.students 这张表使用的是 FEDERATED 存储引擎,它不存储任何真实数据,只是一个“快捷方式”,对这张表执行的所有 INSERT、UPDATE、DELETE 或 SELECT 操作,都会被实时转发到远程服务器上去执行(示例为本机 bbb.student)
mysql> show create table aaa.student;
| student | CREATE TABLE `student` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(64) NOT NULL,
  # .... 省略其他字段
  PRIMARY KEY (`id`),
) ENGINE=FEDERATED DEFAULT CHARSET=utf8 CONNECTION='mysql://user:password@127.0.0.1:3306/bbb/student' |

mysql> show create table bbb.student;
| student | CREATE TABLE `student` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(64) NOT NULL,
  `school_id` int(11) NOT NULL,
  # .... 省略其他字段
  PRIMARY KEY (`id`),
) ENGINE=InnoDB AUTO_INCREMENT=6811210 DEFAULT CHARSET=utf8 |
  • 分析:FEDERATED 引擎的内存消耗主要来自查询执行过程,而非数据缓存。当查询FEDERATED表时,MySQL会将远程表的数据拉取到本地服务器的内存中进行处理,这部分内存由会话级别的缓冲区(如 join_buffer_size、sort_buffer_size)或全局查询执行缓冲区来承载。
  • 分析:FEDERATED 引擎为何会消耗大量内存?主要是全表扫描导致的内存黑洞
官方文档明确指出,如果查询无法使用索引,服务器会从远程表中获取所有行并在本地进行过滤。
这意味着一个查询可能会将整个远程表的数据拉取到本地内存,即使你只加了WHERE或LIMIT条件也是如此。
历史Bug报告甚至记录过,查询一个50GB的FEDERATED表会导致本地MySQL消耗高达50GB的虚拟内存,最终引发系统内存溢出(OOM)。
  • 解决方案:针对本示例,可以将 FEDERATED 表改成视图,这样还可以使用远程表的现有索引
RENAME TABLE aaa.student TO aaa.student_fed_bak;
CREATE VIEW aaa.student AS SELECT * FROM bbb.student;
Copyright © www.sqlfans.cn 2021 - 2026 All Right Reserved更新时间: 2026-09-03 16:54:43

results matching ""

    No results matching ""