4

MySQL内存为什么不断增高,怎么让它释放

 2 years ago
source link: https://blog.51cto.com/u_15773567/5690347
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

MySQL内存为什么不断增高,怎么让它释放

精选 原创

小二上酒8 2022-09-19 17:24:40 ©著作权

文章标签 mysql sql 压测 文章分类 Java 编程语言 阅读数244

问题分析 场景1 使用sysbench压测数据库 场景2 load 一个很大事务的insert语句 问题突破 测试jemalloc 场景1使用sysbench压测数据库 场景2 load 一个很大事务的insert语句 小结 MySQL到底有没有释放内存? 通过gdb调试 结论

线上MySQL数据库发现一些实例,内存使用不断增高,并且当连接数断开后内存不会释放,最终导致的结果是被操作系统OOM

模拟两个场景来分析此问题:

场景1 使用sysbench压测数据库

使用sysbench压测MySQL,等待连接断开后,使用top查看mysqld进程,内存使用情况。

将mysql innodb_buffer_pool设置为128M,方便观察内存增长情况。

启动MySQL后内存使用情况,大约在150M:

MySQL内存为什么不断增高,怎么让它释放_mysql

sysbench压测60s

sysbench --db-driver=mysql /usr/share/sysbench/oltp_read_only.lua --mysql-host=127.0.0.1 --mysql-port=3320 --mysql-db=sbtest --mysql-user=root --mysql-password=123456 --tables=16 --table_size=500000 --threads=128 --time=60 --report-interval=1 run

观察内存使用情况:

MySQL内存为什么不断增高,怎么让它释放_压测_02

内存增长到540M左右,但是内存并没有随着sysbench连接断开而释放。

场景2 load 一个大事务的insert语句

重启MySQL观察内存使用情况, 大约占用170M:

MySQL内存为什么不断增高,怎么让它释放_sql_03

文件大小43M

MySQL内存为什么不断增高,怎么让它释放_压测_04

文件内容:

INSERT INTO dummy VALUES
(1,'This is a dummy value'),
(1,'This is a dummy value'),
(1,'This is a dummy value'),
(1,'This is a dummy value'),
(1,'This is a dummy value'),
(1,'This is a dummy value'),
(1,'This is a dummy value'),
(1,'This is a dummy value'),
(1,'This is a dummy value'),
(1,'This is a dummy value'),
.......

source 文件:

mysql> source test.sql

source完成后观察内存使用情况:

MySQL内存为什么不断增高,怎么让它释放_压测_05

占用到1G,同样也是,没有随着连接断开,内存进行释放。

通过上面两个例子,能证明MySQL连接断开后,内存没有释放。

长时间下去就会导致内存不断增高,查看bugs.mysql 发现有人提过此问题:

 ​https://bugs.mysql.com/bug.php?id=83047​

--里面有人提出load个大事务,复现此问题。

同样percona版本也有人提过:

 ​https://bugs.launchpad.net/ubuntu/+source/mysql-5.7/+bug/1710146​

在上面链接中,有人提到过使用jemalloc好像能够解决此问题。

测试jemalloc

同样是测试上面两种场景, 使用jemalloc方式如下:

yum install jemalloc
在mysqld_safe中,最前面添加如下信息:
export LD_PRELOAD="/lib64/libjemalloc.so.1"
重启启动mysql实例

使用pt工具确认是否是用你了jemalloc
pt-mysql-summary -S /tmp/mysql-3320.sock --user root --password 123456|grep -A 5 "Memory management"
显示如下信息,则代表使用了jemalloc

场景1使用sysbench压测数据库

启动MySQL后内存使用情况,大约在150M:

MySQL内存为什么不断增高,怎么让它释放_压测_06

sysbench压测60s

sysbench --db-driver=mysql /usr/share/sysbench/oltp_read_only.lua --mysql-host=127.0.0.1 --mysql-port=3320 --mysql-db=sbtest --mysql-user=root --mysql-password=123456 --tables=16 --table_size=500000 --threads=128 --time=60 --report-interval=1 run

观察内存使用情况:

MySQL内存为什么不断增高,怎么让它释放_mysql_07

压测时内存增长到550M左右,随着sysbench连接断开而释放了一部分内存, 保持在370M左右。

场景2 load 一个大事务的insert语句

重启MySQL观察内存使用情况, 大约占用150M:

MySQL内存为什么不断增高,怎么让它释放_压测_08

source 文件:

mysql> source test.sql

source完成后观察内存使用情况:

MySQL内存为什么不断增高,怎么让它释放_mysql_09

随着SQL执行完成,内存释放到了300M。

通过以上对比,可以得出,jemalloc内存分配方式,确实可以使内存快速释放给操作系统,减少系统内存使用过高而被OOM

MySQL到底有没有释放内存?

继续分析MySQL实际执行中,到底有没有主动释放内存?在哪里释放的内存?

通过gdb调试

  1. 先登录到mysql中
  2. 另外一个窗口,执行gdb attach 到mysql进程
/opt/rh/devtoolset-8/root/bin/gdb -p `ps -ef|grep -w mysqld|grep -v grep | awk {'print $2'}`
(gdb) b /mydata/Project/mysql-server/sql/sql_parse.cc:1947
(gdb) b connection_handler_per_thread.cc:321
Breakpoint 2 at 0x166ee7f: file /mydata/Project/mysql-server/sql/conn_handler/connection_handler_per_thread.cc, line 321.
  1. gdb 中执行c
(gdb) c
Continuing
  1. mysql客户端source 文件
mysql> source test.sql

此时可能需要等待一段时间,因为文件比较大,mysql client 需要进行解析文件,只要在gdb 窗口等待即可,不用执行任何命令,直到出现下面这种情况:

gdb) c
Continuing.
[Switching to Thread 0x7fea2b008700 (LWP 29406)]

Thread 28 "mysqld" hit Breakpoint 1, dispatch_command (thd=0x7fea02819000, com_data=0x7fea2b007c90, command=COM_QUERY)
at /mydata/Project/mysql-server/sql/sql_parse.cc:1947
1947 free_root(thd->mem_root,MYF(MY_KEEP_PREALLOC));
  1. 此时就可以开始调试,查看MySQL内存使用情况:
(gdb) p thd->main_mem_root
$1 = {free = 0x7fe9c2406020, used = 0x7fe9c29b3020, pre_alloc = 0x7fea02827020, min_malloc = 32, block_size = 8160, block_num = 863,
first_block_usage = 0, max_capacity = 0, allocated_size = 844547312, error_for_capacity_exceeded = 0 '\000', error_handler = 0x14baa8b
<sql_alloc_error_handler()>, m_psi_key = 8}

这里看到allocated_size=844547312 大约是800M,此时mysqld进程占用内存情况:

MySQL内存为什么不断增高,怎么让它释放_mysql_10

执行source 之前的内存使用情况+800M 约等于 目前的1.1g。

输入'n'执行下一步free_root后,在查看内存:

gdb) n
1961 thd->profiling.finish_current_query();
(gdb) p thd->main_mem_root
$2 = {free = 0x7fea02827020, used = 0x0, pre_alloc = 0x7fea02827020, min_malloc = 32, block_size = 8160, block_num = 4,
first_block_usage = 0, max_capacity = 0, allocated_size = 8208, error_for_capacity_exceeded = 0 '\000',
error_handler = 0x14baa8b <sql_alloc_error_handler()>, m_psi_key = 8}

此时allocated_size = 8208,也就是在MySQL层,执行完SQL后内存已经释放了,top看下操作系统情况:

MySQL内存为什么不断增高,怎么让它释放_压测_11

这里使用的是jemalloc,内存进行了释放(当前占有内存约300M),如果是默认的glibc,则内存不会释放。

这里还分析到,内存的释放是SQL语句级别的,并不是整个事务结束后才释放,而是SQL执行完后即释放。

  1. 继续将会话退出,则会触发另外一个断点
MySQL内存为什么不断增高,怎么让它释放_sql_12

会话退出,会接受到COM_QUIT 命令,接着会释放整个线程的内存,这时再看下top中mysqld内存使用情况:

MySQL内存为什么不断增高,怎么让它释放_mysql_13

会看到随着会话退出,内存又释放了40M左右,这部分内存就是mysql中缓存的source 文件大小,在SQL执行完后,会话不退出时,可以通过查看performance 表查看线程内存使用情况:

MySQL内存为什么不断增高,怎么让它释放_mysql_14

能看到,正好是等于文件的大小。

  1. kill 也会导致NET::buff释放
    通过测试,如果SQL执行完成,在另外一个会话Kill此线程,NET::buff也会被释放

这里的NET::buff并没有随着SQL执行完成释放,查看官方文档net_buffer_length的介绍:

MySQL内存为什么不断增高,怎么让它释放_sql_15

大概意思是:

每个客户端会话线程会分配两个缓冲区:连接缓冲区和结果集缓冲区,并且可以动态放大到 max_allowed_packet 系统变量指定的字节大小, 每个 SQL 语句执行完成 之后,结果集缓冲区自动缩小到 net_buffer_length 变量指定的大小。然后文档并没有提到连接缓冲区会被释放,也就是说会被一直缓存,直到连接断开。

  1. mysql中会主动释放内存
  2. 释放内存是语句级别,不是会话级别
  3. NET::buff内存是需要会话退出后,内存才会释放
  4. kill也会释放会话所占有的内存
  5. jemalloc比glibc内存分配机制更好一些,能及时将内存释放给操作系统

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK