记一次艰难的mysql数据库内存泄漏排查经历,以供以后类似问题参考。
客户反应现场部署的系统,mysql数据库进程占用内存持续上涨,最终在占用到5G左右,mysql进程崩溃,日志提示虚拟内存不足。
操作系统:windows server 2012
数据库:mysql 8.0.30
软件对于数据库的使用情况:数据量比较大,在持续不断地写入和查询。
起初怀疑是不是有代码频繁起新连接,而未关闭旧连接,但经查并没有,连接数量比较稳定。
查询当前连接数:
SHOW STATUS LIKE 'Threads_connected';
查询设置的最大连接数:
show variables like "max_connections";
经查,事务都及时提交了,并没有事务积压。
查询当前事务:
SELECT * FROM performance_schema.`processlist`;
经监控观察,并不存在慢sql。
想通过flush query cache清理查询缓存,但执行报错,查询才知道,mysql8.0版本已经取消了查询缓存
执行几个flush命令,均没有任何效果:
FLUSH TABLES;
FLUSH TABLES WITH READ LOCK;
FLUSH QUERY CACHE;
RESET QUERY CACHE;
FLUSH HOSTS;
FLUSH PRIVILEGES;
FLUSH LOGS;
FLUSH TABLES WITH READ LOCK命令执行后还引起了一点小插曲,执行这个命令时会锁表,导致执行时很多事务无法提交(99个事务),我当时一度以为自己找到问题原因了,是我们的系统有大量未提交事务,但后来才发现是自己执行FLUSH TABLES WITH READ LOCK引起的,断开锁表的连接后(即我的heidisql连接),这些事务立即提交了。
总之,各种清理命令,未起到一丁点效果。
经查,临时表空间占用也并不大。
查询临时表空间:
select * from performance_schema.global_status where variable_name like '%tmp%tables';
select * from sys.memory_global_by_current_bytes where event_name like '%temp%';
# 查询总的分配的内存
select * from sys.memory_global_total;
# 查询总的内存分配
SELECT t.EVENT_NAME, t.CURRENT_NUMBER_OF_BYTES_USED FROM performance_schema.memory_summary_global_by_event_name t ORDER BY t.CURRENT_NUMBER_OF_BYTES_USED DESC;
# 查询用户占用的内存
SELECT t.EVENT_NAME, t.CURRENT_NUMBER_OF_BYTES_USED FROM performance_schema.memory_summary_by_user_by_event_name t ORDER BY t.CURRENT_NUMBER_OF_BYTES_USED DESC;
# 查询线程占用的内存
SELECT t.EVENT_NAME, t.CURRENT_NUMBER_OF_BYTES_USED FROM performance_schema.memory_summary_by_thread_by_event_name t ORDER BY t.CURRENT_NUMBER_OF_BYTES_USED DESC;
# 查询连接主机占用的内存
SELECT t.EVENT_NAME, t.CURRENT_NUMBER_OF_BYTES_USED FROM performance_schema.memory_summary_by_host_by_event_name t ORDER BY t.CURRENT_NUMBER_OF_BYTES_USED DESC;
# 查询用户占用的内存
SELECT t.EVENT_NAME, t.CURRENT_NUMBER_OF_BYTES_USED FROM performance_schema.memory_summary_by_account_by_event_name t ORDER BY t.CURRENT_NUMBER_OF_BYTES_USED DESC;
performance_schema观察到的内存总体比较稳定,没有持续上涨,与mysql进程的内存上涨趋势不符,说明mysql的运行可能实际并不需要任务管理器中显示的那么多内存。
尝试设置了innodb_buffer_pool_size等参数,主要效果是启动时mysql进程占用的内存变小了(之前一些参数设置得比较大,改小了),但持续增长问题依旧,没有丝毫缓解。
而且尝试了mysql的默认配置,也没有效果。
去mysql官网查询,升级日志显示,8.0.31版本,修复了两个内存泄露的bug,就怀疑是不是碰上了这个版本mysql的内存泄漏问题,其实官网bug的描述,与我们的情况并不是一个情况,纯属死马当活马医了。
于是在测试服务器上部署了一套8.0.33版本的mysql(绿色部署),将8.0.30的数据全都导入,然后运行观察,发现内存泄漏问题依旧,无任何缓解。
上述一通折腾下来无任何效果,于是静下心观察:
通过以上内存观察,我感觉可能是windows server 2012内存管理机制与mysql的内存管理器不太融洽而导致的问题,为了进行对比,我尝试在个人电脑部署相同版本相同配置的mysql及相同的服务对比运行(我本地请求的频率设置得更高),个人电脑系统为windows 10专业版,结果对比下来果然有发现:
相同的mysql相同的业务服务,而运行结果却大相径庭,于是分析差异原因:
从网上了解的资料,mysql的内存管理可能是释放了内存,但是这些内存并没有返还给操作系统,而是以碎片的形式残留在mysql占用的内存中,而windows server 2012对内存的回收可能并不积极,从而导致mysql进程占用的内存中包含大量的碎片,占用内存不断膨胀而且无法释放,但实际上mysql真正使用的并没有这么多。
而windows 10增强了内存管理,支持自动内存管理,对内存的回收更为积极,所以在本地电脑的mysql的内存虽然会上涨,但也会回落,总体趋于平稳,不会出现内存始终上涨无法释放的问题。
以上为结合资料个人理解,如有谬误,欢迎指正。
基于上述推断,于是考虑从系统内存清理的角度进行尝试,遂找到memreduct这个小巧的windows内存清理工具,memreduct是开源的,代码托管在github,从其官方说明来看,它的原理是通过调用windows api来实现各种内存清理,用户评价颇高。
memreduct官网:Mem Reduct
memreduct代码库:henrypp (henrypp) · GitHub
于是将memreduct安装到测试服务器的windwos server 2012中,执行清理,神奇的事情发生了,mysql的内存占用直接从800+M被清理到了几十兆(当然被进行内存清理的不仅仅是mysql进程,也包含其它进程,比如java进程),而且这是在mysql与业务服务均正常运行的前提下发生的。
memreduct可以设置定时清理与阈值清理,于是本着极端测试的想法,我将其设置为每分钟清理一次,持续观察了几天,mysql服务与业务服务始终可以正常使用,而如果要给客户服务器实施,实际并不需要这么高的频率,只需要每天或者每几天在闲时进行一次清理就足够了。
于是,初步观察下来这似乎是一个可行的有效的解决方案,不过可能实现方式对于用户接受度有限,因为用户会担心清理过程引发一些其它的问题,但目前也未能找到更好的方法。
至此,个人对此次mysql内存泄漏问题排查就结束了,作此笔记,以供参考。
以下命令sql编辑器执行即可。
1、开启日志记录
SET global general_log = 'ON';
2、关闭日志记录
SET global general_log = 'OFF';
3、导出日志
SHOW VARIABLES LIKE 'general_log%';
4、到mysql数据目录下获取截取的sql日志