98%的DBA不知道的數據庫內存知識點
| 作者 鄧英明 ,騰訊云DBA,擅長數據庫架構設計、故障診斷、性能優化,現主要負責騰訊云數據庫MySQL/TDSQL-C/Redis的相關工作。
在日常工作中,時不時會收到內存使用率高的告警,那么我們應該如何處理呢?本文將從Linux和MySQL兩個層面,介紹內存管理的相關知識點,希望能給大家帶來一些幫助,以便更好地應對內存問題。
一、如何看懂內存指標
遇到內存問題,可以先通過free、vmstat、top等命令,進行檢查。free命令,可以獲取系統內存的總體使用情況;vmstat命令,可以實時觀察內存的變化情況;top命令,可以進行排序,獲取內存占用大的進程。這里簡單介紹一下free命令輸出(以CentOS 7為例):
- total used free shared buff/cache available
- Mem: 8008704 5234876 157920 640 2615908 2467292
- Swap: 2047 0 2047
第一行是內存數據
1. total:內存總大小,對應于/proc/meminfo的MemTotal
2. used:已使用的內存大小,對應于/proc/meminfo的(MemTotal - MemFree - Buffers - Cached - Slab)
3. free:未使用的內存大小,對應于/proc/meminfo的MemFree
4. buff/cache:已使用的緩存大小,對應于/proc/meminfo的Buffers+Cached
5. available:可供使用的內存大小,這是一個預估值,對應于/proc/meminfo的MemAvailable
第二行是交換分區數據
1. total:交換分區總大小,對應于/proc/meminfo的SwapTotal
2. used:已使用的交換分區,對應于/proc/meminfo的(SwapTotal - SwapFree)
3. free:未使用的的內存大小,對應于/proc/meminfo的SwapFree
這里值得注意的是,Linux操作系統會最大限度利用內存,空閑內存free少,不代表系統內存不夠用了。個人建議,一方面需要觀察內存增長的整體趨勢是否逐漸趨于平穩、以及used和buff/cache的變化情況;另一方面需要觀察是否頻繁使用到交換分區swap,當然了,這里要避免NUMA和swapiness設置不正確帶來的干擾。
二、MySQL如何使用內存
在MySQL中,內存占用主要包括以下幾部分,全局共享的內存、線程獨占的內存、內存分配器占用的內存,具體如下:
全局共享
1. innodb_buffer_pool_size:InnoDB緩沖池的大小
2. innodb_additional_mem_pool_size:InnoDB存放數據字典和其他內部數據結構的內存大小,5.7已被移除
3. innodb_log_buffer_size:InnoDB日志緩沖的大小
4. key_buffer_size:MyISAM緩存索引塊的內存大小
5. query_cache_size:查詢緩沖的大小,8.0已被移除
線程獨占
1. thread_stack:每個線程分配的堆棧大小
2. sort_buffer_size:排序緩沖的大小
3. join_buffer_size:連接緩沖的大小
4. read_buffer_size:MyISAM順序讀緩沖的大小
5. read_rnd_buffer_size:MyISAM隨機讀緩沖的大小、MRR緩沖的大小
6. tmp_table_size/max_heap_table_size:內存臨時表的大小
7. binlog_cache_size:二進制日志緩沖的大小
內存分配器
在MySQL中,buffer pool的內存,是通過mmap()方式直接向操作系統申請分配;除此之外,大多數的內存管理,都需要經過內存分配器。為了實現更高效的內存管理,避免頻繁的內存分配與回收,內存分配器會長時間占用大量內存,以供內部重復使用。關于內存分配器的選擇,推薦使用jemalloc,可以有效解決內存碎片與提升整體性能。
因此,MySQL占用內存高的原因可能包括:innodb_buffer_pool_size設置過大、連接數/并發數過高、大量排序操作、內存分配器占用、以及MySQL Bug等等。一般來說,在MySQL整個運行周期內,剛啟動時內存上漲會比較快,運行一段時間后會逐漸趨于平穩,這種情況是不需要過多關注的;如果在穩定運行后,出現內存突增、內存持續增長不釋放的情況,那就需要我們進一步分析是什么原因造成的。
三、到底是誰占用了內存
在絕大多數情況下,我們是不需要花費過多精力,去關注MySQL內存使用情況的; 但是,也不能排除確實存在內存占用異常的情況,這個時候我們應該如何去進行深入排查呢? 其實,MySQL官方就提供了強大的實時監控工具——performance_schema庫下的監控內存表,通過這個工具,我們可以很清晰地觀察到MySQL內存到底是被誰占用了、分別占用了多少。
開啟內存監控
實例啟動時開啟
我們可以選擇,在實例啟動時,開啟內存監控采集器,具體方法如下:
- vi my.cnf
- performance-schema-instrument='memory/%=ON'
禁用方法如下:
- vi my.cnf
- performance-schema-instrument='memory/%=OFF'
實例運行時開啟
我們也可以選擇 ,在實 例運 行時,動態開啟內存監控采集器,具體方法如下:
- mysql> UPDATE performance_schema.setup_instruments SET ENABLED = 'YES' WHERE NAME LIKE 'memory/%';
禁用方法如下:
- mysql> UPDATE performance_schema.setup_instruments SET ENABLED = 'NO' WHERE NAME LIKE 'memory/%';
因為采集器的實現原理,是在內存進行分配/回收時,更新相對應內存監控表的數據;換句話說,就是采集器只能監控到開啟之后的內存使用情況;而MySQL很大一部分內存都是在實例啟動時就預先分配的,因此要想準確監控實例的內存使用率,需要在實例啟動時就開啟內存采集器。
內存監控表
在performance_schema庫下,提供多個維度的內存監控表,具體如下:
memory_summary_by_account_by_event_name: 賬號緯度的內存監控表
memory_summary_by_host_by_event_name: 主機緯度的內存監控表
memory_summary_by_thread_by_event_name: 線程維度的內存監控表
memory_summary_by_user_by_event_name: 用戶緯度的內存監控表
memory_summary_global_by_event_name: 全局緯度的內存監控表
內存監控表均包括以下關鍵字段:
COUNT_ALLOC: 內存分配次數
C OUNT_FREE: 內存回收次數
S UM_NUMBER_OF_BYTES_ALLOC: 內存分配大小
SUM_NUMBER_OF_BYTES_FREE: 內存回收大小
CURRENT_COUNT_USED: 當前分配的內存,通過COUNT_ALLOC-COUNT_FREE計算得到
CURRENT_NUMBER_OF_BYTES_USED: 當前分配的內存大小,通過SUM_NUMBER_OF_BYTES_ALLOC-SUM_NUMBER_OF_BYTES_FREE計算得到
LOW_COUNT_USED: CURRENT_COUNT_USED的最小值
HIGH_COUNT_USED: CURRENT_COUNT_USED的最大值
LOW_NUMBER_OF_BYTES_USED: CURRENT_NUMBER_OF_BYTES_USED的最小值
HIGH_NUMBER_OF_BYTES_USED: CURRENT_NUMBER_OF_BYTES_USED的最大值
接下來,讓我們看一個正常運行實例的內存使用情況,具體如下:
- mysql> select USER,HOST,EVENT_NAME,COUNT_ALLOC,COUNT_FREE,CURRENT_COUNT_USED,SUM_NUMBER_OF_BYTES_ALLOC,SUM_NUMBER_OF_BYTES_FREE,CURRENT_NUMBER_OF_BYTES_USED from performance_schema.memory_summary_by_account_by_event_name order by CURRENT_NUMBER_OF_BYTES_USED desc limit 10;
- +------+-----------+----------------------------+-------------+------------+--------------------+---------------------------+--------------------------+------------------------------+
- | USER | HOST | EVENT_NAME | COUNT_ALLOC | COUNT_FREE | CURRENT_COUNT_USED | SUM_NUMBER_OF_BYTES_ALLOC | SUM_NUMBER_OF_BYTES_FREE | CURRENT_NUMBER_OF_BYTES_USED |
- +------+-----------+----------------------------+-------------+------------+--------------------+---------------------------+--------------------------+------------------------------+
- | NULL | NULL | memory/innodb/buf_buf_pool | 32 | 0 | 32 | 4500488192 | 0 | 4500488192 |
- | NULL | NULL | memory/innodb/os0event | 1573559 | 0 | 1573559 | 214004024 | 0 | 214004024 |
- | NULL | NULL | memory/innodb/hash0hash | 82 | 6 | 76 | 397976480 | 227067024 | 170909456 |
- | NULL | NULL | memory/innodb/log0log | 10 | 0 | 10 | 33565840 | 0 | 33565840 |
- | root | localhost | memory/innodb/std | 3650638 | 3043111 | 607527 | 160778066 | 141334898 | 19443168 |
- | NULL | NULL | memory/mysys/KEY_CACHE | 3 | 0 | 3 | 8390768 | 0 | 8390768 |
- | NULL | NULL | memory/innodb/ut0pool | 2 | 0 | 2 | 4194480 | 0 | 4194480 |
- | NULL | NULL | memory/innodb/sync0arr | 3 | 0 | 3 | 2506184 | 0 | 2506184 |
- | NULL | NULL | memory/innodb/lock0lock | 33 | 0 | 33 | 2245040 | 0 | 2245040 |
- | root | localhost | memory/innodb/mem0mem | 9897784 | 9896793 | 991 | 8845389160 | 8843147749 | 2241411 |
- +------+-----------+----------------------------+-------------+------------+--------------------+---------------------------+--------------------------+------------------------------+
- 10 rows in set (0.01 sec)
再看一個Bug #86821的場景,buffer pool占用最大內存正常,但是存儲過程占用3GB就比較異常了,存在內存泄漏的風險;由此可知,通過內存監控表,我們可以快速定位內存異常占用問題。
- mysql> select event_name, current_alloc, high_alloc from memory_global_by_current_bytes where current_count > 0;
- +--------------------------------------------------------------------------------+---------------+-------------+
- | event_name | current_alloc | high_alloc |
- +--------------------------------------------------------------------------------+---------------+-------------+
- | memory/innodb/buf_buf_pool | 7.29 GiB | 7.29 GiB |
- | memory/sql/sp_head::main_mem_root | 3.21 GiB | 3.62 GiB |
- | memory/innodb/hash0hash | 210.16 MiB | 323.63 MiB |
- | memory/sql/TABLE | 183.82 MiB | 190.28 MiB |
- | memory/sql/Query_cache | 128.02 MiB | 128.02 MiB |
- | memory/mysys/KEY_CACHE | 64.00 MiB | 64.00 MiB |
- | memory/innodb/log0log | 32.08 MiB | 32.08 MiB |
- | memory/innodb/parallel_doublewrite | 30.27 MiB | 30.27 MiB |
- | memory/performance_schema/table_handles | 27.19 MiB | 27.19 MiB |
- | memory/innodb/mem0mem | 19.14 MiB | 20.79 MiB |
- | memory/performance_schema/events_statements_history_long | 13.66 MiB | 13.66 MiB |
- | memory/performance_schema/events_statements_summary_by_digest.tokens | 9.77 MiB | 9.77 MiB |
另外,如果我們在內存監控表,看見一些比較陌生的event,可以翻閱官方文檔或源碼,繼續進一步解讀,例如
memory/innodb/os0event
- /** @file include/os0event.h
- The interface to the operating system condition variables
- Created 2012-09-23 Sunny Bains (split from os0sync.h)
- *******************************************************/
memory/innodb/hash0hash
- /** @file include/hash0hash.h
- The simple hash table utility
- Created 5/20/1997 Heikki Tuuri
- *******************************************************/
四、總結
總的來說,只要我們的操作系統/數據庫有一個相對合理的配置(NUMA、swapiness、jemalloc 、innodb_buffer_pool_size等等),大多數情況是不需要關注內存問題的; 如果非常不幸運地碰到內存占用異常問題,可以通過官方提供的實時監控工具——內存監控表,快速進行定位; 不過需要注意的是,開啟內存采集器也會帶來一些問題,比如額外的內存占用和性能損耗,一般建議是在系統出現內存問題之后,再重啟實例啟用,并等待復現。