問題分析場景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:
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
觀察內存使用情況:
內存增長到540M左右,但是內存並沒有隨著sysbench連接斷開而釋放
重啟MySQL觀察內存使用情況, 大約佔用170M:
文件大小43M
文件內容:
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完成後觀察內存使用情況:
佔用到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方式如下:
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
啟動MySQL後內存使用情況,大約在150M:
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
觀察內存使用情況:
壓測時內存增長到550M左右,隨著sysbench連接斷開而釋放了一部分內存, 保持在370M左右。
重啟MySQL觀察內存使用情況, 大約佔用150M:
source 文件:
mysql> source test.sql
source完成後觀察內存使用情況:
隨著SQL執行完成,內存釋放到了300M。
通過以上對比,可以得出,jemalloc內存分配方式,確實可以使內存快速釋放給作業系統,減少系統內存使用過高而被OOM
MySQL到底有沒有釋放內存?繼續分析MySQL實際執行中,到底有沒有主動釋放內存?在哪裡釋放的內存?
通過gdb調試先登錄到mysql中
另外一個窗口,執行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.
gdb 中執行c
(gdb) c
Continuing
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));
此時就可以開始調試,查看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進程佔用內存情況:
執行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看下作業系統情況:
這裡使用的是jemalloc,內存進行了釋放(當前佔有內存約300M),如果是默認的glibc,則內存不會釋放。
這裡還分析到,內存的釋放是SQL語句級別的,並不是整個事務結束後才釋放,而是SQL執行完後即釋放。
繼續將會話退出,則會觸發另外一個斷點
會話退出,會接受到COM_QUIT 命令,接著會釋放整個線程的內存,這時再看下top中mysqld內存使用情況:
會看到隨著會話退出,內存又釋放了40M左右,這部分內存就是mysql中緩存的source 文件大小,在SQL執行完後,會話不退出時,可以通過查看performance 表查看線程內存使用情況:
能看到,正好是等於文件的大小。
kill 也會導致NET::buff釋放
通過測試,如果SQL執行完成,在另外一個會話Kill此線程,NET::buff也會被釋放
這裡的NET::buff並沒有隨著SQL執行完成釋放,查看官方文檔net_buffer_length的介紹:
大概意思是:
每個客戶端會話線程會分配兩個緩衝區:連接緩衝區和結果集緩衝區,並且可以動態放大到 max_allowed_packet 系統變量指定的字節大小, 每個 SQL 語句執行完成 之後,結果集緩衝區自動縮小到 net_buffer_length 變量指定的大小。然後文檔並沒有提到連接緩衝區會被釋放,也就是說會被一直緩存,直到連接斷開。
mysql中會主動釋放內存
釋放內存是語句級別,不是會話級別
NET::buff內存是需要會話退出後,內存才會釋放
kill也會釋放會話所佔有的內存
jemalloc比glibc內存分配機制更好一些,能及時將內存釋放給作業系統