如何调试导致 mysql 超出其自身限制的数据库内存泄漏?

Sim*_*mon 9 mysql

我们遇到了一个应用程序的数据库服务器之一的问题,可能是由于某些代码在 Mysql 管理其内存的方式中造成了问题。

直到 4 月的第二周,我们的 db 服务器稳定消耗了大约 5 gigs 的内存(最多 7 gigs)。但随后,它开始无限增加,甚至超过了理论上的最大可能分配。

这是我们的年度 munin 图表,显示了过去 2 个月的增长情况:


(来源:postimg.org

这是在 mysql 中重新启动后的最后 7 天的另一个视图:


(来源:postimg.org

这是由 mysqltuner.pl 创建的报告:

-  -  -  -  性能指标  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  - ---------

[--] 最多:4d 1h 56m 28s(152M q [431.585 qps],383K conn,TX:593B,RX:29B)
[--] 读取/写入:90% / 10%
[--] 总缓冲区:5.3G 全局 + 每线程 10.2M(200 个最大线程)

[OK] 最大可能内存使用量:7.3G(已安装 RAM 的 46%)
[OK] 慢查询:0% (2K/152M)
[OK] 可用连接的最高使用率:13% (26/200)
[OK] 密钥缓冲区大小/MyISAM 索引总数:16.0M/300.0K
[OK] 键缓冲区命中率:100.0%(61M 缓存/9 次读取)
[OK] 查询缓存效率:70.8%(103M cached / 146M selects)
[!!] 每天查询缓存修剪:501819
[OK] 需要临时表的排序:0%(926 次临时排序 / 3M 排序)
[!!] 没有索引的连接:39128
[OK] 在磁盘上创建的临时表:16%(磁盘上 821K / 总共 5M)
[OK] 线程缓存命中率:99%(创建 26 个 / 383K 个连接)
[!!] 表缓存命中率:10%(845打开/7K打开)
[OK] 使用的打开文件限制:3% (148/4K)
[OK] 立即获得表锁:99%(65M 立即/65M 锁)
[!!] InnoDB 数据大小/缓冲池:5.5G/5.0G

我们在这里处于未知领域。任何帮助将不胜感激!

编辑:添加 my.cnf

# MySQL 数据库服务器配置文件。

[客户]
端口 = 3306
socket = /var/run/mysqld/mysqld.sock

[mysqld_safe]
socket = /var/run/mysqld/mysqld.sock
不错 = 0

[mysqld]
character_set_server = utf8
collat​​ion_server = utf8_general_ci

用户 = mysql
socket = /var/run/mysqld/mysqld.sock
pid-file = /var/run/mysqld/mysqld.pid
端口 = 3306
basedir = /usr
数据目录 = /var/lib/mysql
tmpdir = /tmp
跳过外部锁定
绑定地址 = 0.0.0.0

# 微调
最大连接数 = 200
key_buffer = 16M
max_allowed_pa​​cket = 16M
线程堆栈 = 192K
join_buffer_size = 2M
sort_buffer_size = 2M
read_buffer_size = 2M
read_rnd_buffer_size = 4M
线程缓存大小 = 128
线程并发= 24
table_cache = 2K
table_open_cache = 2K
table_definition_cache = 4K

# 这将替换启动脚本并在需要时检查 MyISAM 表
#他们第一次被触动
myisam-recover = 备份

# innodb
innodb_buffer_pool_size = 5G
innodb_flush_log_at_trx_commit = 1
innodb_support_xa = 1
innodb_additional_mem_pool_size = 32M
innodb_log_buffer_size = 8M
innodb_flush_method = O_DIRECT

# 查询缓存配置
query_cache_limit = 32M
query_cache_size = 256M
query_cache_min_res_unit = 256

# 日志记录和复制
log_error = /var/log/mysql/error.log
日志慢查询 = /var/log/mysql/slow.log
long_query_time = 1

# 复制配置
log_bin = /var/log/mysql/mysql-bin.log
日志-bin = mysql-bin
expire_logs_days = 15
同步二进制日志 = 1
服务器 ID = 1

ssl-ca =/etc/ssl/private/repl/cacert.pem
ssl-cert =/etc/ssl/private/repl/master-cert.pem
ssl-key =/etc/ssl/private/repl/master-key.pem

[mysqldump]
快的
引用名称
max_allowed_pa​​cket = 16M

[isamchk]
key_buffer = 16M                                

Mic*_*bot 5

...甚至超过理论上的最大可能分配。

[OK] Maximum possible memory usage: 7.3G (46% of installed RAM)
Run Code Online (Sandbox Code Playgroud)

实际上没有办法计算 MySQL 的最大可能内存使用量,因为它可以从系统请求的内存没有上限。

mysqltuner.pl 所做的计算只是一个估计,基于一个不考虑所有可能变量的公式,因为如果考虑所有可能的变量,答案将始终是“无限”。不幸的是它被这样标记。

以下是我对导致内存使用过多的原因的理论:

thread_cache_size       = 128
Run Code Online (Sandbox Code Playgroud)

鉴于您的max_connections设置为 200,128 的值thread_cache_size似乎太高了。这让我认为这可能会导致您的问题:

当不再需要某个线程时,分配给它的内存将被释放并返回到系统,除非该线程返回到线程缓存中。在这种情况下,内存仍保持分配状态。

http://dev.mysql.com/doc/refman/5.6/en/memory-use.html

如果您的工作负载导致偶尔的客户端线程需要大量内存,那么这些线程可能会保留该内存,然后返回池并闲置,继续保留它们在技术上“不需要”的内存前提是,如果您可能再次需要它,保留内存比释放它的成本要低。

我认为在首先记下 MySQL 目前使用了多少内存之后,值得尝试执行以下操作。

注意当前缓存了多少线程:

mysql> show status like 'Threads_cached';
+----------------+-------+
| Variable_name  | Value |
+----------------+-------+
| Threads_cached | 9     |
+----------------+-------+
1 row in set (0.00 sec)
Run Code Online (Sandbox Code Playgroud)

接下来,禁用线程缓存。

mysql> SET GLOBAL thread_cache_size = 0;
Run Code Online (Sandbox Code Playgroud)

这会禁用线程缓存,但缓存的线程将保留在池中,直到再次使用它们。断开与服务器的连接,然后重新连接并重复。

mysql> show status like 'Threads_cached';
Run Code Online (Sandbox Code Playgroud)

继续断开连接、重新连接并检查,直到计数器达到 0。

然后,查看 MySQL 占用了多少内存。

您可能会看到下降,可能是显着的下降,但也可能不会。我在我的一个系统上对此进行了测试,该系统的缓存中有 9 个线程。一旦这些线程全部从缓存中清除,MySQL 持有的总内存确实减少了……减少幅度不大,但它确实说明了缓存中的线程在被销毁时至少释放了一些内存。

如果你看到明显的下降,你可能已经发现了你的问题。如果您不这样做,那么还需要发生另一件事,并且发生的速度取决于您的环境。

如果理论认为其他线程(当前为活动客户端连接提供服务的线程)分配了大量内存,要么是因为当前客户端会话中最近的工作,要么是因为需要大量内存的工作已由另一个线程完成在它们在池中停滞之前建立连接,那么您将不会看到内存消耗的所有潜在减少,直到这些线程被允许终止并被销毁。想必您的应用程序不会永远保留它们,但是需要多长时间才能确定是否存在差异取决于您是否可以选择循环应用程序(删除并重新连接客户端线程)或者是否可以选择只是等待它们随着时间的推移自行断开并重新连接。

但是……这似乎是一次值得的测试。thread_cache_size通过设置为 0,您不应该看到显着的性能损失。幸运的是,thread_cache_size它是一个动态变量,因此您可以在服务器运行时自由更改它。