问题剖析场景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内存为什么不断增高,怎么让它释放
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内存为什么不断增高,怎么让它释放
内存增长到540M左右,可是内存并没有跟着sysbench衔接断开而开释。

场景2 load 一个大业务的insert句子

重启MySQL调查内存运用状况, 大约占用170M:

MySQL内存为什么不断增高,怎么让它释放
文件巨细43M

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

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内存为什么不断增高,怎么让它释放
占用到1G,相同也是,没有跟着衔接断开,内存进行开释。

问题打破

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

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

bugs.mysql.com/bug.php?id=…

–里边有人提出load个大业务,复现此问题。

相同percona版本也有人提过:

bugs.launchpad.net/ubuntu/+sou…

在上面链接中,有人说到过运用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内存为什么不断增高,怎么让它释放
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内存为什么不断增高,怎么让它释放
压测时内存增长到550M左右,跟着sysbench衔接断开而开释了一部分内存, 保持在370M左右。

场景2 load 一个大业务的insert句子

重启MySQL调查内存运用状况, 大约占用150M:

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

mysql> source test.sql

source完结后调查内存运用状况:

MySQL内存为什么不断增高,怎么让它释放
跟着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'}`
  1. 设置断点
(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内存为什么不断增高,怎么让它释放
履行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内存为什么不断增高,怎么让它释放
这儿运用的是jemalloc,内存进行了开释(当时占有内存约300M),假如是默许的glibc,则内存不会开释。

这儿还剖析到,内存的开释是SQL句子级别的,并不是整个业务结束后才开释,而是SQL履行完后即开释。

  1. 持续将会话退出,则会触发别的一个断点

MySQL内存为什么不断增高,怎么让它释放
会话退出,会接受到COM_QUIT 指令,接着会开释整个线程的内存,这时再看下top中mysqld内存运用状况:

MySQL内存为什么不断增高,怎么让它释放
会看到跟着会话退出,内存又开释了40M左右,这部分内存就是mysql中缓存的source 文件巨细,在SQL履行完后,会话不退出时,能够经过检查performance 表检查线程内存运用状况:

MySQL内存为什么不断增高,怎么让它释放
能看到,正好是等于文件的巨细。

  1. kill 也会导致NET::buff开释

    经过测验,假如SQL履行完结,在别的一个会话Kill此线程,NET::buff也会被开释

这儿的NET::buff并没有跟着SQL履行完结开释,检查官方文档net_buffer_length的介绍:

MySQL内存为什么不断增高,怎么让它释放
大约意思是:

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

定论

  1. mysql中会主动开释内存

  2. 开释内存是句子级别,不是会话级别

  3. NET::buff内存是需求会话退出后,内存才会开释

  4. kill也会开释会话所占有的内存

  5. jemalloc比glibc内存分配机制更好一些,能及时将内存开释给操作体系