在一个阳光明媚的下午,电脑右下角传来一片片邮件提醒,同时伴随着微信钉钉的震动,打开一看,应用各类出错,天兔告警,数据库服务器内存爆红,Mysql数据库实例挂掉了。php
先交代一下数据库版本:html
mysql> status -------------- mysql Ver 14.14 Distrib 5.7.22-22, for Linux (x86_64) using 6.2 Connection id: 59568 Current database: Current user: root@localhost SSL: Not in use Current pager: stdout Using outfile: '' Using delimiter: ; Server version: 5.7.22-22-log Percona Server (GPL), Release 22, Revision f62d93c Protocol version: 10
崩溃故障排除毫不是一项有趣的任务,特别是若是MySQL没有报告崩溃的缘由。例如,当MySQL内存不足时。mysql
数据库邮件告警提醒发来的消息:sql
Type: mysql Tags: 生产主库 Host: 172.16.1.66:3306 Level: critical Item: connect Value: down Message: mysql server down
登陆 Grafana 监控面板,数据库链接在哪一个时间段曾有幅度的增加。数据库
顺手检查一下以前的服务器邮件监控告警记录,上一个时间点,内存占用率99%,这说明了数据库链接的幅度增加,多是压垮服务器的最后一根稻草。服务器
其实致使OOM的直接缘由并不复杂,就是由于服务器内存不足,内核须要回收内存,回收内存就是kill掉服务器上使用内存最多的程序,而MySQL服务可能就是使用内存最多,因此就OOM了。微信
Type: os Tags: 66数据库 Host: 172.16.1.66: Level: critical Item: memory Value: 99% Message: too more memory usage
咱们带着这个疑问来排查一下日志:并发
# 查看日志 tail -500f /var/log/messages # 如下是 oom-killer Nov 27 14:55:48 itstyledb1 kernel: mysqld invoked oom-killer: gfp_mask=0x201da, order=0, oom_score_adj=0 Nov 27 14:55:48 itstyledb1 kernel: mysqld cpuset=/ mems_allowed=0-1 Nov 27 14:55:48 itstyledb1 kernel: CPU: 2 PID: 895 Comm: mysqld Kdump: loaded Not tainted 3.10.0-862.3.2.el7.x86_64 #1 Nov 27 14:55:48 itstyledb1 kernel: Hardware name: Huawei RH1288 V3/BC11HGSC0, BIOS 3.22 05/16/2016 Nov 27 14:55:48 itstyledb1 kernel: Call Trace:
小伙伴们继续往下看:ssh
0 pages HighMem/MovableOnly Nov 27 14:55:48 itstyledb1 kernel: 291281 pages reserved Nov 27 14:55:48 itstyledb1 kernel: [ pid ] uid tgid total_vm rss nr_ptes swapents oom_score_adj name Nov 27 14:55:48 itstyledb1 kernel: [ 468] 0 468 28271 4326 62 55 0 systemd-journal Nov 27 14:55:48 itstyledb1 kernel: [ 490] 0 490 11492 2 24 553 -1000 systemd-udevd Nov 27 14:55:48 itstyledb1 kernel: [ 787] 0 787 13877 18 27 96 -1000 auditd Nov 27 14:55:48 itstyledb1 kernel: [ 810] 81 810 14552 81 34 89 -900 dbus-daemon Nov 27 14:55:48 itstyledb1 kernel: [ 815] 0 815 55956 1 60 466 0 abrtd Nov 27 14:55:48 itstyledb1 kernel: [ 816] 0 816 55327 9 64 346 0 abrt-watch-log Nov 27 14:55:48 itstyledb1 kernel: [ 818] 0 818 121607 220 90 495 0 NetworkManager Nov 27 14:55:48 itstyledb1 kernel: [ 822] 0 822 5415 49 16 33 0 irqbalance Nov 27 14:55:48 itstyledb1 kernel: [ 823] 997 823 134634 97 60 1306 0 polkitd Nov 27 14:55:48 itstyledb1 kernel: [ 825] 0 825 6594 42 20 41 0 systemd-logind Nov 27 14:55:48 itstyledb1 kernel: [ 830] 0 830 31578 28 21 139 0 crond Nov 27 14:55:48 itstyledb1 kernel: [ 839] 0 839 27522 2 10 31 0 agetty Nov 27 14:55:48 itstyledb1 kernel: [ 1142] 0 1142 143454 114 97 2672 0 tuned Nov 27 14:55:48 itstyledb1 kernel: [ 1144] 0 1144 28203 11 59 246 -1000 sshd Nov 27 14:55:48 itstyledb1 kernel: [ 1145] 0 1145 97438 694 103 328 0 rsyslogd Nov 27 14:55:48 itstyledb1 kernel: [ 1369] 0 1369 22526 20 44 256 0 master Nov 27 14:55:48 itstyledb1 kernel: [ 1371] 89 1371 22596 32 46 251 0 qmgr Nov 27 14:55:48 itstyledb1 kernel: [ 5140] 0 5140 5102 1617 15 239 0 mysqld_exporter Nov 27 14:55:48 itstyledb1 kernel: [ 9430] 0 9430 55966 378 62 790 0 snmpd Nov 27 14:55:48 itstyledb1 kernel: [30320] 27 30320 22951376 13928375 43437 8163662 0 mysqld Nov 27 14:55:48 itstyledb1 kernel: [ 688] 89 688 22552 271 46 0 0 pickup Nov 27 14:55:48 itstyledb1 kernel: Out of memory: Kill process 30320 (mysqld) score 984 or sacrifice child Nov 27 14:55:48 itstyledb1 kernel: Killed process 30320 (mysqld) total-vm:91805504kB, anon-rss:55713500kB, file-rss:0kB, shmem-rss:0kB Nov 27 14:56:00 itstyledb1 systemd: mysqld.service: main process exited, code=killed, status=9/KILL Nov 27 14:56:00 itstyledb1 systemd: Unit mysqld.service entered failed state. Nov 27 14:56:00 itstyledb1 systemd: mysqld.service failed. Nov 27 14:56:00 itstyledb1 systemd: mysqld.service holdoff time over, scheduling restart. Nov 27 14:56:01 itstyledb1 systemd: Starting MySQL Server...
当out of memory发生时,out_of_memory函数会选择一个内核认为犯有分配过多内存 “罪行”的进程,并杀死该进程。显然 Mysql 就是哪一个“罪人”。ide
随后 MySql 会自动重启。重启之后,内存是下来了,可是临近下班的时候,差很少又又又占满了。
[root@itstyledb1 ~]# free -m total used free shared buff/cache available Mem: 55803 54976 241 10 585 349 Swap: 32064 25036 7028
找到MySql进程,执行如下top -p pid,内存使用52.4g
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 935 mysql 20 0 79.7g 52.4g 7336 S 0.3 96.1 255:44.76 mysqld
1)查看MySQL全局占用多少内存
SELECT (@@innodb_buffer_pool_size +@@innodb_log_buffer_size +@@key_buffer_size) / 1024 /1024 AS MEMORY_MB;
查询结果为:
+----------------+ | MEMORY_MB | +----------------+ | 20512.00000000 | +----------------+
2)查看performance_schema占用多少内存
SELECT SUBSTRING_INDEX(event_name,'/',2) AS code_area, sys.format_bytes(SUM(current_alloc)) AS current_alloc FROM sys.x$memory_global_by_current_bytes GROUP BY SUBSTRING_INDEX(event_name,'/',2) ORDER BY SUM(current_alloc) DESC;
查询结果为:
+---------------------------+---------------+ | code_area | current_alloc | +---------------------------+---------------+ | memory/performance_schema | 349.80 MiB | +---------------------------+---------------+
3)查看每一个线程占用多少内存
SELECT ( ( @@read_buffer_size + @@read_rnd_buffer_size + @@sort_buffer_size + @@join_buffer_size + @@binlog_cache_size + @@thread_stack + @@max_allowed_packet + @@net_buffer_length ) ) / (1024*1024) AS MEMORY_MB;
查询结果为:
+-----------+ | MEMORY_MB | +-----------+ | 87.5156 | +-----------+
查看当前线程
show full processlist
最终结果为:
+-----------+ | MEMORY_MB | +-----------+ | 87.5156*37| +-----------+
4)查看 memory 存储引擎占用多少内存
SELECT SUM(max_data_length)/1024/1024 AS MEMORY_MB FROM information_schema.tables WHERE ENGINE='memory';
查询结果为:
+---------------+ | MEMORY_MB | +---------------+ | 3857.37713909 | +---------------+
以上四项加起来差很少也就27975MB,差不错28G的样子,可是 MySql 进程显示占用了52.4G,那么剩下24.4G去哪了?
此线程池非彼链接池,其实二者是有很大区别的,链接池通常在客户端设置,而线程池是在DB服务器上配置;另外链接池能够取到避免了链接频繁建立和销毁,可是没法取到控制MySQL活动线程数的目标,在高并发场景下,没法取到保护DB的做用。比较好的方式是将链接池和线程池结合起来使用。
关于线程池的一些参数:
mysql> show variables like 'thread%'; +-------------------------------+---------------------------+ | Variable_name | Value | +-------------------------------+---------------------------+ | thread_handling | one-thread-per-connection | | thread_pool_high_prio_mode | transactions | | thread_pool_high_prio_tickets | 4294967295 | | thread_pool_idle_timeout | 60 | | thread_pool_max_threads | 100000 | | thread_pool_oversubscribe | 3 | | thread_pool_size | 12 | | thread_pool_stall_limit | 500 | +-------------------------------+---------------------------+
该参数是配置线程模型,默认状况是one-thread-per-connection,也就是不启用线程池。将该参数设置为pool-of-threads即启用了线程池。
该参数是设置线程池的Group的数量,默认为系统CPU的个数,充分利用CPU资源。
该参数设置group中的最大线程数,每一个group的最大线程数为thread_pool_oversubscribe+1,注意listener线程不包含在内。
高优先级队列的控制参数,有三个值(transactions/statements/none),默认是transactions,三个值的含义以下:
transactions:对于已经启动事务的语句放到高优先级队列中,不过还取决于后面的thread_pool_high_prio_tickets参数
statements:这个模式全部的语句都会放到高优先级队列中,不会使用到低优先级队列
该参数控制每一个链接最多语序多少次被放入高优先级队列中,默认为4294967295,注意这个参数只有在thread_pool_high_prio_mode为transactions的时候才有效果。
worker线程最大空闲时间,默认为60秒,超过限制后会退出。
该参数用来限制线程池最大的线程数,超过该限制后将没法再建立更多的线程,默认为100000。
该参数设置timer线程的检测group是否异常的时间间隔,默认为500ms。
最终配置以下:
#thread pool thread_handling=pool-of-threads #Group的数量,默认为系统CPU的个数,充分利用CPU资源 thread_pool_size=24 #每一个group的最大线程数为thread_pool_oversubscribe+1 thread_pool_oversubscribe=3 performance_schema=off #extra connection,防止线程池满的状况下没法登陆MySQL extra_max_connections = 8 extra_port = 33333
备注:线程池在Percona,MariaDB,Oracle MySQL企业版中提供,Oracle MySQL社区版并不提供。
线程池貌似并不会直接致使内存不回收,网上有说同时开启Thread pool和PS会出现内存泄露,可是
目前Percona server 5.7.21-20+版本已经修复了这个问题,显然是不存在的。
因为是生产环境,这个问题拖得时间有点长,那么慢查询会不会影响内存使用问题呢?带着这个问题,查看了慢查询后台列表,在数据库奔溃的前一个时间段,的确有很多慢查询语句。可是这并不能在必定程度上说明问题,因为服务器的 MySql 服务在杀死以前,内存已经见底,此时链接数并很少,也就三四十来个左右,大多处于休眠状态,而且此时已经占用了大部分的Swap空间。也就是说,在资源有限的状况下一定会出现很多慢查询语句。
其实这个"意外"一点也不意外,其实已经发生了屡次了。可是仍是作个小结吧,由于最终没有确认问题出如今哪里,因此仍是发布了吧,万一有专业的DBA遇到相似的问题还能够小小的解惑一下。
https://blog.52itstyle.vip/archives/3554/
https://bugs.mysql.com/bug.php?id=91861
https://bugs.mysql.com/bug.php?id=91710
https://dev.mysql.com/doc/refman/5.7/en/memory-use.html
https://dev.mysql.com/doc/refman/5.7/en/thread-pool-tuning.html