1. 程式人生 > 資料庫 >MySQL OOM(記憶體溢位)的解決思路

MySQL OOM(記憶體溢位)的解決思路

OOM全稱"Out Of Memory",即記憶體溢位。

記憶體溢位已經是軟體開發歷史上存在了近40年的“老大難”問題。在作業系統上執行各種軟體時,軟體所需申請的記憶體遠遠超出了實體記憶體所承受的大小,就叫記憶體溢位。

記憶體溢位產生原因多種多樣,當記憶體嚴重不足時,核心有兩種選擇:

  1. 直接panic
  2. 殺掉部分程序,釋放一些核心。

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

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

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

通過日誌可以看出哪些程序、佔用多少記憶體等資訊,並會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 cached
 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_YOUNG from 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的介紹:一條命令解讀InnoDB儲存引擎—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語法、資料型別、資料大小等這些因素都與記憶體有關,所以在設計使用上更要多想記憶體溢位問題。

以上就是MySQL OOM(記憶體溢位)的解決思路的詳細內容,更多關於MySQL OOM(記憶體溢位)的解決的資料請關注我們其它相關文章!