MySQL OOM(內存溢出)的排查思路及優化方法

​OOM全稱"Out Of Memory",即內存溢出。

內存溢出已經是軟件開發歷史上存在了近40年的“老大難”問題。在操作系統上運行各種軟件時,軟件所需申請的內存遠遠超出了物理內存所承受的大小,就叫內存溢出。

內存溢出產生原因多種多樣,當內存嚴重不足時,內核有兩種選擇:

  1. 直接panic

  2. 殺掉部分進程,釋放一些內核。

大部分情況下,會殺掉導致OOM的進程,然後系統恢復。通常我們會添加對內存的監控報警,例如:當memory或swap使用超過90%時,觸發報警通知,需要及時介入排查。

如果已經出現OOM,則可以通過dmesg命令查看,CentOS7版本以上支持 -T選項,能將時間戳轉成時間格式,方便查看具體時間:

[root@localhost ~]# dmesg -T[Tue Mar 26 10:23:41 2019] memory: usage 25164884kB, limit 25165824kB, failcnt 172713[Tue Mar 26 10:23:41 2019] memory+swap: usage 25165824kB, limit 25165824kB, failcnt 6632[Tue Mar 26 10:23:41 2019] kmem: usage 0kB, limit 9007199254740988kB, failcnt 0[Tue Mar 26 10:23:41 2019] Memory cgroup stats for host: cache:4184KB rss:25160700KB rss_huge:0KB 
mapped_file:1968KB swap:940KB inactive_anon:1744836KB active_anon:23417824KB 
inactive_file:76KB active_file:1112KB unevictable:0KB[Tue Mar 26 10:23:41 2019] [ pid ] uid  tgid total_vm      rss nr_ptes swapents oom_score_adj name[Tue Mar 26 10:23:41 2019] [12910] 997 12910 16489842  6282421   28321        0           809 mysqld[Tue Mar 26 10:23:41 2019] Memory cgroup out of memory: Kill process 548216 (ParalInputsProc) 
score 1812 or sacrifice child[Tue Mar 26 10:23:41 2019] Killed process 12910 (mysqld) total-vm:65959368kB, anon-rss:25129684kB, 
file-rss:0kB, shmem-rss:0kB

通過日誌可以看出哪些進程、佔用多少內存等信息,並會Kill掉佔用內存較大的進程。

 

--------------------------------內存問題的排查思路--------------------------------

一、操作系統內存檢查

已MySQL爲例,OOM後,mysqld進程被Killed,內存會被釋放。mysqld_safe安全進程會將mysqld拉起,此時查看到的系統內存會是一個正常值。如果內存使用很高,但還未OOM,系統內存使用情況可能爲下面情況:

[root@localhost ~]# free -m             total       used       free     shared    buffers     cachedMem:        128937     128527        409          1        166       1279-/+ buffers/cache:     127081       1855Swap:        16383      16252        131

可以看出此時的內存使用已經很高了,物理內存和swap虛擬內存幾乎都被用完,buffers和cached也不多,隨時可能出現OOM的情況。

首先,通過top命名查看佔用內存最大的進程:

shift+o可以選擇排序方式,n代表%MEM。

[root@localhost ~]# topMem:  132031556k total, 131418864k used,   612692k free,   212104k buffersSwap: 16777212k total,        0k used, 16777212k free, 14648144k cache  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND14920 mysql     20   0  125g 109g 6164 S  6.6 87.0  27357:08 mysqld 

可以看出mysqld進程佔用內存最大,也可以這樣查:

[root@localhost ~]# ps -e -o 'pid,comm,args,pcpu,rsz,vsz,stime,user,uid'|grep -E 'PID|mysql'|grep -v grep  PID COMMAND         COMMAND                     %CPU   RSZ    VSZ STIME USER       UID25339 mysqld          /export/servers/mysql/bin/m  9.4 115001324 130738976 2017 mysql 50032070 mysqld_safe     /bin/sh /export/servers/mys  0.0   296 106308  2017 root         0

RSZ爲進程佔用私有內存大小,單位Kb。
VSZ爲映射的虛擬內存大小,單位Kb。

通過RSZ/total 也可以算出佔用總內存比例。

二、查看給mysql分配的內存

mysql內部主要內存可通過下面語句查出:

MYSQL >SET @giga_bytes = 1024*1024*1024;SELECT (@@key_buffer_size + @@query_cache_size + @@tmp_table_size + @@innodb_buffer_pool_size + @@innodb_additional_mem_pool_size + @@innodb_log_buffer_size + (select count(HOST) from information_schema.processlist)/*@@max_connections*/*(@@read_buffer_size + @@read_rnd_buffer_size + @@sort_buffer_size + @@join_buffer_size + @@binlog_cache_size + @@thread_stack)) / @giga_bytes AS MAX_MEMORY_GB;

每個參數配置大小:

*************************** 1. row ***************************                @@key_buffer_size: 67108864               @@query_cache_size: 0                 @@tmp_table_size: 268435456        @@innodb_buffer_pool_size: 38654705664@@innodb_additional_mem_pool_size: 134217728         @@innodb_log_buffer_size: 8388608                @@max_connections: 3000               @@read_buffer_size: 4194304           @@read_rnd_buffer_size: 4194304               @@sort_buffer_size: 2097152               @@join_buffer_size: 2097152              @@binlog_cache_size: 32768                   @@thread_stack: 262144

每個參數配置說明:

innodb_buffer_pool_size 佔用內存最大的參數
innodb_additional_mem_pool_size 額外內存,mysql5.7以後移除
innodb_log_buffer_size 重做日誌緩存大小
key_buffer_size 只用於MyISAM引擎,不需要太大
tmp_table_size 臨時表緩存大小
query_cache_size 查詢緩存,建議關閉
max_connections 最大連接數
read_buffer_size  
read_rnd_buffer_size  
sort_buffer_size  
join_buffer_size  
binlog_cache_size  
thread_stack
這些參數都跟線程有關,所佔內存爲這些參數的和*最大連接數。連接數越多佔用內存也就越多,建議不超過512K,binlog_cache_size採用系統默認32K,thread_stack默認256K即可。

需要給mysql分配多大內存,直接跟以上參數有關。太大會導致內存不足,太小會影響性能,如何分配合理值,還需根據業務情況來定。但業務場景較多,每個業務配置都不一樣,會造成運維成本較高。所以能定製出一套適用於絕大多數場景的配置模板就可以了。

1、如果mysql分配的內存比系統內存大
比如系統內存128G,mysql分配的內存已經大於128G,但是系統本身和其它程序也需要內存,甚至mysqldump同樣需要內存,所以這樣就很容易造成系統內存不足,從而導致OOM。這時我們要查出哪些參數設置比較大,適當降低內存分配。

innodb_buffer_pool在mysql中佔有最大內存,將innodb_buffer_pool_size調小可以有效降低OOM問題。但如果設置太小會導致內存刷髒頁頻率增加,IO增多,從而降低性能。通常我們認爲innodb_buffer_pool_size爲系統內存的60%~75%最優。

查看buffer_pool的使用情況:

MYSQL >select POOL_ID,POOL_SIZE,FREE_BUFFERS,DATABASE_PAGES,OLD_DATABASE_PAGES,
MODIFIED_DATABASE_PAGES,PAGES_MADE_YOUNG,PAGES_NOT_MADE_YOUNGfrom information_schema.INNODB_BUFFER_POOL_STATS;+---------+-----------+--------------+----------------+--------------------+-------------------------+------------------+----------------------+| POOL_ID | POOL_SIZE | FREE_BUFFERS | DATABASE_PAGES | OLD_DATABASE_PAGES | MODIFIED_DATABASE_PAGES | PAGES_MADE_YOUNG | PAGES_NOT_MADE_YOUNG |+---------+-----------+--------------+----------------+--------------------+-------------------------+------------------+----------------------+|       0 |    611669 |         1024 |         610644 |             225393 |                       0 |           309881 |                    0 ||       1 |    611669 |         1024 |         610645 |             225393 |                       0 |           309816 |                    0 ||       2 |    611669 |         1024 |         610645 |             225393 |                       0 |           309756 |                    0 |+---------+-----------+--------------+----------------+--------------------+-------------------------+------------------+----------------------+

可以看出buffer_pool分了3個實例,POOL_SIZE爲每個實例大小,這裏爲頁個數,我們知道mysql頁的默認大小爲16K,所以單個實例的真正大小爲611669*16K,5.6以後要求FREE_BUFFERS至少保留1024個頁,少於1024個頁時會強制刷髒數據,後面的值可以看出髒頁情況。另外如果PAGES_MADE_YOUNG遠大於PAGES_NOT_MADE_YOUNG頁數,那麼此時內存使用就可能比較大,可以適當降低innodb_buffer_pool_size大小。

另一篇文章也有對buffer_pool的介紹:show engine innodb status 詳解

如果innodb_buffer_pool_size不是很大,但內存還是很高,也可能是由於併發線程太多導致,需要確認是不是應用異常,還是需要調整max_connections最大連接。如果連接太多,每個連接也會佔用獨立的內存,read、sort、join緩存都是session級別,連接越多需要內存就越多,所以這些參數也不能設置太大。

需要注意的是一些參數不支持動態修改,只能先修改配置文件然後重啓mysql才能生效,所以在mysql啓動之前,一定要把參數值確認好。

2、如果mysql分配的內存比系統內存小
如果mysql參數設置都比較合理,但是仍然出現oom,那麼可能是由於mysql在系統層面所需內存不足導致,因爲mysql讀取表時,如果同時有多個session引用一個表則會創建多個表對象,這樣雖然減少了內部表鎖的爭用,但是會加大內存使用量。

首先,可以通過lsof -p pid查看進程打開的系統文件數,pid爲mysqld的進程號。

[root@localhost ~]# ps -ef | grep mysqld[root@localhost ~]# lsof -p 3455COMMAND   PID  USER   FD   TYPE  DEVICE    SIZE/OFF      NODE NAMEmysqld  30012 mysql  cwd    DIR     8,3       12288  58982404 /mysql/datamysqld  30012 mysql  mem    REG     8,1      599392    272082 /lib64/libm-2.12.somysqld  30012 mysql  mem    REG     8,1       91096    272089 /lib64/libz.so.1.2.3mysqld  30012 mysql  mem    REG     8,1       93320    272083 /lib64/libgcc_s-4.4.7-20120601.so.1mysqld  30012 mysql  mem    REG     8,1       43392    272095 /lib64/libcrypt-2.12.somysqld  30012 mysql   10uW  REG     8,3   536870912  59015176 /mysql/data/ib_logfile0mysqld  30012 mysql   11uW  REG     8,3   536870912  59015177 /mysql/data/ib_logfile1mysqld  30012 mysql   12uW  REG     8,3   536870912  59015178 /mysql/data/ib_logfile2mysqld  30012 mysql   13uW  REG     8,3   675282944  59001816 /mysql/data/test/table6.ibdmysqld  30012 mysql   14uW  REG     8,3  2155872256  58985613 /mysql/data/test/table487.ibdmysqld  30012 mysql   15u   REG     8,3           0  58982414 /mysql/tmp/ibhNDzPM (deleted)mysqld  30012 mysql   16uW  REG     8,3  2306867200  58983861 /mysql/data/test/table327.ibdmysqld  30012 mysql   17uW  REG     8,3  4169138176  58985467 /mysql/data/test/table615.ibdmysqld  30012 mysql   18uW  REG     8,3    79691776  59020641 /mysql/data/test/table_v199_20170920.ibdmysqld  30012 mysql   19uW  REG     8,3    67108864  59015043 /mysql/data/test/table_v39_20170920.ibdmysqld  30012 mysql   20uW  REG     8,3    75497472  59014992 /mysql/data/test/table_v7_20170920.ibdmysqld  30012 mysql   21uW  REG     8,3    83886080  59019735 /mysql/data/test/table_v167_20170920.ibdmysqld  30012 mysql   22uW  REG     8,3  1367343104  58997684 /mysql/data/popfin6/table_uuid6.ibdmysqld  30012 mysql   23uW  REG     8,3  1275068416  58984491 /mysql/data/test/table_uuid7.ibd...[root@localhost ~]# lsof -p 3455 |grep ibd|wc -l54869

查看mysql服務打開文件數限制:

MySQL >show global variables like 'open_files_limit';+------------------+-------+| Variable_name    | Value |+------------------+-------+| open_files_limit | 65535 |+------------------+-------+

查看操作系統打開文件數限制:

[root@localhost ~]# ulimit  -amax memory size         (kbytes, -m) unlimitedopen files                      (-n) 65535

如果此時打開的文件很多,內存也會佔用很多。

其次,還需看一下table_open_cache,當打開一個表後會把這個表的文件描述符緩存下來。

MYSQL >show global variables like 'table_open_cache';+------------------+-------+| Variable_name    | Value |+------------------+-------+| table_open_cache | 16384 |+------------------+-------+MYSQL >show global status like '%open%tables%';+------------------------+--------+| Variable_name          | Value  |+------------------------+--------+| Open_tables            | 16384  || Opened_tables          | 401374 |+------------------------+--------+

通過以上兩個值來判斷 table_open_cache 是否到達瓶頸。
當緩存中的值open_tables 臨近到了 table_open_cache 值的時候,說明表緩存池快要滿了,但Opened_tables 還在一直有新的增長,這說明還有很多未被緩存的表。

用show open tables from schema命令,可以查看table_open_cache中緩存的表,重複打開的表僅顯示一個。

MYSQL >show open tables from sysbenchtest;+--------------+----------+--------+-------------+| Database     | Table    | In_use | Name_locked |+--------------+----------+--------+-------------+| sysbenchtest | sbtest1  |      1 |           0 || sysbenchtest | sbtest2  |      0 |           0 || sysbenchtest | sbtest3  |      0 |           0 || sysbenchtest | sbtest4  |      0 |           0 || sysbenchtest | sbtest5  |      0 |           0 |

In_use顯示當前正在使用此表的線程數,如果大於0也意味着此表被鎖。

Name_locked只適用於DROP和RENAME,在執行DROP或RENAME時,table_open_cache中的表文件描述符會被移除,所以不會看到除0以外的其他值。

一般在庫表比較多的情況下(分庫分表)很容易出現內存佔用較大的情況。如果要解決根源,還是需要對庫表進行拆分。

3、MYSQL內部其他內存

information_schema下的表都使用的都是MEMORY存儲引擎,數據只在內存中保留,啓動時加載,關閉後釋放。

查看除系統庫外是否有MEMORY引擎表:

MySQL >select * from information_schema.tables where engine='MEMORY'
and TABLE_SCHEMA !='information_schema';

如果業務有使用MEMORY存儲引擎的,儘量改成innodb引擎。

4、MYSQL事件內存指標

從MySQL5.7開始,在performance_schema中會記錄內存分配。

查看哪些指標啓動了內存收集功能:

MySQL >select * from performance_schema.setup_instruments where NAME LIKE 'memory/%';

啓動需要收集內存的指標:

MySQL >UPDATE performance_schema.setup_instruments SET ENABLED = 'YES' 
WHERE NAME LIKE 'memory/%';

指標的內存收集結果會彙總到到sys庫下的視圖中:

MySQL root@[sys]>show tables like 'memory%';+-----------------------------------+| Tables_in_sys (memory%)           |+-----------------------------------+| memory_by_host_by_current_bytes   || memory_by_thread_by_current_bytes || memory_by_user_by_current_bytes   || memory_global_by_current_bytes    || memory_global_total               |+-----------------------------------+

這些視圖總結了內存使用情況,按事件類型分組,默認降序排列:

MySQL >select event_name,current_count,current_alloc,high_alloc 
from sys.memory_global_by_current_bytes where current_count > 0;+--------------------------------------------------------------------------------+---------------+---------------+-------------+| event_name                                                                     | current_count | current_alloc | high_alloc  |+--------------------------------------------------------------------------------+---------------+---------------+-------------+| memory/performance_schema/table_handles                                        |            10 | 90.62 MiB     | 90.62 MiB   || memory/performance_schema/events_statements_summary_by_thread_by_event_name    |             3 | 26.01 MiB     | 26.01 MiB   || memory/performance_schema/memory_summary_by_thread_by_event_name               |             3 | 16.88 MiB     | 16.88 MiB   || memory/performance_schema/events_statements_history_long                       |             1 | 13.66 MiB     | 13.66 MiB   || memory/performance_schema/events_statements_history                            |             3 | 10.49 MiB     | 10.49 MiB   || memory/performance_schema/events_statements_current                            |             3 | 10.49 MiB     | 10.49 MiB   |...

總結:

通過以上排查能大體知道哪些佔用內存較多,針對內存佔用較多的地方再做具體優化。正像文章開頭所說的,內存溢出已經是軟件開發歷史上存在了近40年的“老大難”問題,何況數據庫環境更加複雜,SQL語法、數據類型、數據大小等因素都與內存有關,所以在設計使用上更要多想內存溢出問題。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章