日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > windows >内容正文

windows

系统无法分配所需内存_Innodb内存管理解析

發布時間:2024/9/27 windows 39 豆豆
生活随笔 收集整理的這篇文章主要介紹了 系统无法分配所需内存_Innodb内存管理解析 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

本文主要介紹innodb的內存管理,涉及基礎的內存分配結構、算法以及buffer pool的實現細節,提及change buffer、自適應hash index和log buffer的基本概念和內存基本配比,側重點在內存的分配和管理方式。本文所述內容基于mysql8.0版本。

基礎內存分配

在5.6以前的版本中,innodb內部實現了除buffer pool外的額外內存池,那個時期lib庫中的分配器在性能和擴展性上表現比較差,缺乏針對多核系統優化的內存分配器,像linux下最通用的ptmalloc的前身是Doug Lea Malloc,也是因為不支持多線程而被棄用了。所以innodb自己實現了內存分配器,使用額外的內存池來響應那些原本要發給系統的內存請求,用戶可以通過設置參數innodb_use_sys_malloc 來選擇使用innodb的分配器還是系統分配器,使用innodb_additional_mem_pool_size參數設定額外內存池的大小。隨著多核系統的發展,一些分配器對內部實現進行了優化和擴展,已經可以很好的支持多線程,相較于innodb特定的內存分配器可以提供更好的性能和擴展性,所以這個實現在5.6版本已經棄用,5.7版本刪除。本文所討論的內容和涉及到的代碼基于mysql8.0版本。

innodb內部封裝了基礎分配釋放方式malloc,free,calloc,new,delete等,在開啟pfs模式下,封裝內部加入了內存追蹤信息,使用者可傳入對應的key值來記錄某個event或者模塊的內存分配信息,這部分信息通過pfs內部表來對外展示,以便分析內存泄漏、內存異常等問題。

innodb內部也提供了對系統基礎分配釋放函數封裝的allocator,用于std::*容器內部的內存分配,可以讓這些容器內部的隱式分配走innodb內部封裝的接口,以便于內存信息的追蹤。基礎的malloc/calloc等封裝后也會走allocator的接口。

基礎封裝:

非UNIV_PFS_MEMORY編譯模式 #define UT_NEW(expr, key) ::new (std::nothrow) expr #define UT_NEW_NOKEY(expr) ::new (std::nothrow) expr #define UT_DELETE(ptr) ::delete ptr #define UT_DELETE_ARRAY(ptr) ::delete[] ptr #define ut_malloc(n_bytes, key) ::malloc(n_bytes) #define ut_zalloc(n_bytes, key) ::calloc(1, n_bytes) #define ut_malloc_nokey(n_bytes) ::malloc(n_bytes) ...打開UNIV_PFS_MEMORY #define UT_NEW(expr, key) ::new (ut_allocator<byte>(key).allocate(sizeof expr, NULL, key, false, false)) expr #define ut_malloc(n_bytes, key) static_cast<void *>(ut_allocator<byte>(key).allocate( n_bytes, NULL, UT_NEW_THIS_FILE_PSI_KEY, false, false))#define ut_zalloc(n_bytes, key) static_cast<void *>(ut_allocator<byte>(key).allocate( n_bytes, NULL, UT_NEW_THIS_FILE_PSI_KEY, true, false))#define ut_malloc_nokey(n_bytes) static_cast<void *>( ut_allocator<byte>(PSI_NOT_INSTRUMENTED) .allocate(n_bytes, NULL, UT_NEW_THIS_FILE_PSI_KEY, false, false))...

可以看到在非UNIV_PFS_MEMORY編譯模式下,直接調用系統的分配函數,忽略傳入的key,而UNIV_PFS_MEMORY編譯模式下使用ut_allocator分配,下面有ut_allocator的介紹,比較簡單的封裝。

memory heap

主要管理結構為mem_heap_t,8.0中的實現比較簡單,內部維護block塊的鏈表,包含指向鏈表開始和尾部的指針可以快速找到鏈表頭部和尾部的節點,每個節點都是mem_heap_t的結構。mem_heap在創建的時候會初始一塊內存作為第一個block,大小可由使用者指定。mem_heap_alloc響應基本的內存分配請求,先嘗試從block中切分出滿足請求大小的內存,如果不能滿足則創建一個新的block,新的block size至少為上一個block的兩倍(last block),直到達到規定的上限值,新創建的block總是鏈到鏈表的尾部。mem_heap_t中記錄了block的size和鏈表中block的總size以及分配模式(type)等信息,基本結構如下圖:

innodb在內部定義了三種分配block模式供選擇:

  • MEM_HEAP_DYNAMIC 使用malloc動態分配,比較通用的分配模式
  • MEM_HEAP_BUFFER size滿足一定條件,使用buffer pool中的內存塊
  • MEM_HEAP_BTR_SEARCH 保留額外的內存,地址保存在free_block中
  • 一般的使用模式為MEM_HEAP_DYNAMIC,也可以使用b|c的模式,在某些情況下使用free_block中的內存。mem heap鏈表中的block在最后統一free,按照分配模式走不同的free路徑。

    基本思想也是使用一次性分配大內存塊,再從大內存塊中切分來響應小內存分配請求,以避免多次調用malloc/free,減少overhead。實現上比較簡單,內部只是將多個大小可能不一的內存塊使用鏈表鏈起來,大多場景只有一個block。沒有內存歸還、復用和合并等機制,使用過程中不會將內存free,會有一定程度的內存浪費,但有效減少了內存碎片,比較適用于短周期多次分配小內存的場景。

    基礎allocator

    ut_allocator

    innodb內部提供ut_allocator用來為std::* 容器分配內存,內部封裝了基礎的內存分配釋放函數,以便于內存追蹤和統一管理。

    ut_allocator提供基本的allocate/deallocate的分配釋放函數,用于分配n個對象所需內存,內部使用malloc/free。另外也提供了大內存塊的分配,開啟LINUX_LARGE_PAGES時使用HugePage,在服務器內存比較大的情況可以減少頁表條目提高檢索效率,未開啟時使用mmap/munmap內存映射的方式。

    更多細節見如下代碼:

    /** Allocator class for allocating memory from inside std::* containers. */ template <class T> class ut_allocator {// 分配n個elements所需內存大小,內部使用malloc/calloc分配// 不開啟PFS_MEMORY分配n_elements * sizeof(T)大小的內存// 開啟PFS_MEMORY多分配sizeof(ut_new_pfx_t)大小的內存用于信息統計pointer allocate(size_type n_elements, const_pointer hint = NULL,PSI_memory_key key = PSI_NOT_INSTRUMENTED,bool set_to_zero = false, bool throw_on_error = true);// 釋放allocated()分配的內存,內部使用free釋放 void deallocate(pointer ptr, size_type n_elements = 0)// 分配一塊大內存,如果開啟了LINUX_LARGE_PAGES使用HugePage// 否則linux下使用mmappointer allocate_large(size_type n_elements, ut_new_pfx_t *pfx)// 對應allocate_large,linux下使用munmapvoid deallocate_large(pointer ptr, const ut_new_pfx_t *pfx)// 提供顯示構造/析構funcvoid construct(pointer p, const T &val) { new (p) T(val); }void destroy(pointer p) { p->~T(); }開啟PFS_MEMORY場景下提供更多可用func:pointer reallocate(void *ptr, size_type n_elements, PSI_memory_key key);// allocate+construct的封裝, 分配n個單元的內存并構造相應對象實例pointer new_array(size_type n_elements, PSI_memory_key key);// 同上,deallocate+destroy的封裝void delete_array(T *ptr); }

    mem_heap_allocator

    mem_heap_t的封裝,可以作為stl allocator來使用,內部使用mem_heap_t的內存管理方式。

    /** A C++ wrapper class to the mem_heap_t routines, so that it can be used as an STL allocator */ template <typename T> class mem_heap_allocator {mem_heap_allocator(mem_heap_t *heap) : m_heap(heap) {}pointer allocate(size_type n, const_pointer hint = 0) {return (reinterpret_cast<pointer>(mem_heap_alloc(m_heap, n * sizeof(T))));}void deallocate(pointer p, size_type n) {}// 提供顯示構造/析構funcvoid construct(pointer p, const T &val) { new (p) T(val); }void destroy(pointer p) { p->~T(); }private:mem_heap_t *m_heap; }

    buddy

    innodb支持創建壓縮頁以減少數據占用的磁盤空間,支持1K, 2K, 4K 、8K和16k大小的page。buddy allocator用于管理壓縮page的內存空間,提高內存使用率和性能。

    buffer pool中: UT_LIST_BASE_NODE_T(buf_buddy_free_t) zip_free[BUF_BUDDY_SIZES_MAX];/** Struct that is embedded in the free zip blocks */ struct buf_buddy_free_t {union {ulint size; /*!< size of the block */byte bytes[FIL_PAGE_DATA];} stamp;buf_page_t bpage; /*!< Embedded bpage descriptor */UT_LIST_NODE_T(buf_buddy_free_t) list; };

    伙伴系統也是比較經典的內存分配算法,也是linux內核用于解決外部碎片的一種手段。innodb的實現在算法上與buddy的基本實現并無什么區別,所支持最小的內存塊為1k(2^10),最大為16k,每種內存塊維護一個鏈表,多種內存塊鏈表組成了zip_free鏈表。

    分配入口在buf_buddy_alloc_low,先嘗試從zip_free[i]中獲取所需大小的內存塊,如果當前鏈表中沒有,則嘗試從更大的內存塊鏈表中獲取,獲取成功則進行切分,一部分返回另一塊放入對應free鏈表中,實際上是buf_buddy_alloc_zip的一個遞歸調用,只是傳入的i不斷增加。如果一直遞歸到16k的塊都沒法滿足,則從buffer pool中新申請一塊大內存塊,并將其按照伙伴關系進行(比如現分配了16,需要2k,先切分8k,8k,再將其中一個8k切分為4k,4k,再將其中4k切分為2k,2k)切分直到滿足分配請求。

    釋放入口在buf_buddy_free_low,為了避免碎片在釋放的時候多做了一些事情。在釋放一個內存塊的時候沒有直接放回對應鏈表中,而是先查看其伙伴是不是free的,如果是則進行合并,再嘗試對合并后的內存塊進行合并。如果其伙伴是在USED的狀態,這里做了一次relocate操作,將其內容拷貝到其它free的block塊上,再進行對它合并。這種做法有效減少了碎片的存在,但拷貝這種操作也降低了性能。

    buffer pool

    buffer pool是innodb主內存中一塊區域,用于緩存主表和索引中的數據,讀線程可以直接從buffer pool中讀取相應數據從而避免io提升讀取性能,當一個頁面需要修改時,先在buffer pool中進行修改,另有后臺線程來負責刷臟頁。一般在專用服務器中,會將80%的內存分配給buffer pool使用。數據庫啟動時就會將內存分配給buffer pool,不過內存有延遲分配的優化,這部分內存在未真正使用前是沒有進行物理映射的,所以只會影響虛存大小。buffer pool的內存在運行期間不會收縮還給系統,在數據庫關閉時將這部分內存統一釋放。可以設置多個buffer pool實例。

    buffer pool中使用chunk內存塊來管理內存,每個buffer pool實例包含一個或多個chunk,chunks在buffer pool初始化時使用mmap分配,并初始化為多個block。每個block地址相差UNIV_PAGE_SIZE,UNIV_PAGE_SIZE一般是16kb,這塊內存包含了page相關控制信息和真正的數據page兩部分,之后將這些page加入free list中供使用。這里直接使用了mmap而非malloc,是因為在glibc的ptmalloc內存分配器中,大于MMAP_THRESHOLD閥值的內存請求也是使用mmap,MMAP_THRESHOLD默認值是128k,buffer pool的配置大小一般會遠大于128k。

    數據結構

    buffer pool進行內存管理的主要數據結構。

    buf_pool_t

    控制buffer pool的主結構,內部包含多種邏輯鏈表以及相關鎖信息、統計信息、hash table、lru和flush算法相關等信息。

    struct buf_pool_t {//鎖相關 BufListMutex chunks_mutex; /*!< protects (de)allocation of chunks*/BufListMutex LRU_list_mutex; /*!< LRU list mutex */BufListMutex free_list_mutex; /*!< free and withdraw list mutex */BufListMutex zip_free_mutex; /*!< buddy allocator mutex */BufListMutex zip_hash_mutex; /*!< zip_hash mutex */ib_mutex_t flush_state_mutex; /*!< Flush state protection mutex */BufPoolZipMutex zip_mutex; /*!< Zip mutex of this buffer */// index、各種size、數量統計ulint instance_no; /*!< Array index of this buffer pool instance */ulint curr_pool_size; /*!< Current pool size in bytes */ulint LRU_old_ratio; /*!< Reserve this much of the buffer pool for "old" blocks */ #ifdef UNIV_DEBUGulint buddy_n_frames; #endifut_allocator<unsigned char> allocator; // 用于分配chunksvolatile ulint n_chunks; /*!< number of buffer pool chunks */volatile ulint n_chunks_new; /*!< new number of buffer pool chunks */buf_chunk_t *chunks; /*!< buffer pool chunks */buf_chunk_t *chunks_old; ulint curr_size; /*!< current pool size in pages */ulint old_size; /*!< previous pool size in pages */// hash table, 用于索引相關數據頁page_no_t read_ahead_area; hash_table_t *page_hash; hash_table_t *page_hash_old; hash_table_t *zip_hash; // 統計信息相關ulint n_pend_reads; ulint n_pend_unzip; time_t last_printout_time;buf_buddy_stat_t buddy_stat[BUF_BUDDY_SIZES_MAX + 1];buf_pool_stat_t stat; /*!< current statistics */buf_pool_stat_t old_stat; /*!< old statistics */// flush相關BufListMutex flush_list_mutex; FlushHp flush_hp; UT_LIST_BASE_NODE_T(buf_page_t) flush_list;ibool init_flush[BUF_FLUSH_N_TYPES];ulint n_flush[BUF_FLUSH_N_TYPES];os_event_t no_flush[BUF_FLUSH_N_TYPES];ib_rbt_t *flush_rbt; ulint freed_page_clock; ibool try_LRU_scan; lsn_t track_page_lsn; /* Pagge Tracking start LSN. */lsn_t max_lsn_io;UT_LIST_BASE_NODE_T(buf_page_t) free;UT_LIST_BASE_NODE_T(buf_page_t) withdraw;ulint withdraw_target; // lru 相關LRUHp lru_hp;LRUItr lru_scan_itr;LRUItr single_scan_itr;UT_LIST_BASE_NODE_T(buf_page_t) LRU;buf_page_t *LRU_old; ulint LRU_old_len; UT_LIST_BASE_NODE_T(buf_block_t) unzip_LRU; #if defined UNIV_DEBUG || defined UNIV_BUF_DEBUGUT_LIST_BASE_NODE_T(buf_page_t) zip_clean; #endif /* UNIV_DEBUG || UNIV_BUF_DEBUG */UT_LIST_BASE_NODE_T(buf_buddy_free_t) zip_free[BUF_BUDDY_SIZES_MAX];buf_page_t *watch; };

    buf_page_t

    數據頁的控制信息,包含數據頁的大部分信息,page_id、size、引用計數(io,buf),access_time(用于lru調整),page_state以及壓縮頁的一些信息。

    class buf_page_t {public:page_id_t id;page_size_t size;uint32_t buf_fix_count;buf_io_fix io_fix;buf_page_state state;the flush_type. @see buf_flush_t */unsigned flush_type : 2;unsigned buf_pool_index : 6;page_zip_des_t zip; #ifndef UNIV_HOTBACKUPbuf_page_t *hash; #endif /* !UNIV_HOTBACKUP */ #ifdef UNIV_DEBUGibool in_page_hash; /*!< TRUE if in buf_pool->page_hash */ibool in_zip_hash; /*!< TRUE if in buf_pool->zip_hash */ #endif /* UNIV_DEBUG */UT_LIST_NODE_T(buf_page_t) list; #ifdef UNIV_DEBUGibool in_flush_list; ibool in_free_list; #endif /* UNIV_DEBUG */FlushObserver *flush_observer; /*!< flush observer */lsn_t newest_modification;lsn_t oldest_modification;UT_LIST_NODE_T(buf_page_t) LRU; #ifdef UNIV_DEBUGibool in_LRU_list; #endif /* UNIV_DEBUG */ #ifndef UNIV_HOTBACKUPunsigned old : 1; unsigned freed_page_clock : 31; unsigned access_time; #ifdef UNIV_DEBUGibool file_page_was_freed; #endif /* UNIV_DEBUG */ #endif /* !UNIV_HOTBACKUP */ };

    邏輯鏈表

    free list

    包含空閑的pages,當需要從buffer pool中分配空閑塊時從free list中摘取,當free list為空時需要從LRU、unzip_LRU或者flush list中進行淘汰或刷臟以填充free list。buffer pool初始化時創建多個chunks,劃分的pages都加入free list中待使用。

    LRU list

    最重要的鏈表,也是緩存占比最高的鏈表。buffer pool使用lru算法來管理緩存的數據頁,所有從磁盤讀入的數據頁都會先加入到lru list中,也包含壓縮的page。新的page默認加入到鏈表的3/8處(old list),等待下次讀取并滿足一定條件后再從old list加入到young list中,以防止全表掃描污染lru list。需要淘汰時從鏈表的尾部進行evict。

    unzip_LRU list

    是LRU list的一個子集,每個節點包含一個壓縮的page和指向對應解壓后的page的指針。

    flush list

    已修改過還未寫到磁盤的page list,按修改時間排序,帶有最老的修改的page在鏈表的最尾部。當需要刷臟時,從flush list的尾部開始遍歷。

    zip_clean list

    包含從磁盤讀入還未解壓的page,page一旦被解壓就從zip_clean list中刪除并加入到unzip_LRU list中。

    zip_free

    用于buddy allocator的空閑block list,buddy allcator是專門用于壓縮的page(buf_page_t)和壓縮的數據頁的分配器。

    主要操作

    初始化buffer pool

    buffer pool在db啟動時調用buffer_pool_create進行初始化,使用mmap創建配置大小的虛擬內存,并劃分為多個chunks,其它字段基本使用calloc分配(memset)。

    buf_page_get_gen

    從buffer pool中讀取page,比較重要的操作。通過page_id獲取對應的page,如果buffer pool中有這個page則封裝一些信息后返回,如果沒有則需要從磁盤中讀入。讀取模式分為以下7種:

    NORMAL

    在page hash table中查找(hash_lock s模式),如果找到增加bufferfix cnt并釋放hash_lock,如果該page不在buffer pool中則以sync模式從磁盤中讀取,并加入對應的邏輯鏈表中,判讀是否需要線性預讀。如果第一次訪問buffer pool中的該page,設置訪問時間并判斷是否需要線性預讀。判斷是否需要加入到young list中。

    SCAN

    如果page不在buffer pool中使用異步讀取磁盤的模式,不做隨機預讀和線性預讀,不設置訪問時間不加入young list,其它與normal一樣。

    IF_IN_POOL

    只在buffer pool中查找page,如果沒有找到則返回NOT_FOUND。

    PEEK_IF_IN_POOL

    這種模式僅僅用來drop 自適應hash index,跟IF_IN_POOL類似,只是不加入young list,不做線性預讀。

    NO_LATCH

    讀取并設置buffefix,但是不加鎖。

    IF_IN_POOL_OR_WATCH

    與IF_IN_POOL類似,只在buffer pool中查找此page,如果沒有則設置watch。

    POSSIBLY_FREED

    與normal類似,只是允許執行過程中page被釋放。

    buf_LRU_get_free_block

    從buffer pool的free list中摘取一個空閑頁,如果free list為空,移除lru鏈表尾部的block到free list中。這個函數主要是用于用戶線程需要一個空閑block來讀取數據頁。具體操作如下:

  • 如果free list不為空,從中摘取并返回。否則轉下面的操作。
  • 如果buffer pool設置了try_LRU_scan,遍歷lru鏈表嘗試從尾部釋放一個空閑塊加入free list中。如果unzip_LRU鏈表不為空,則先嘗試從unzip_LRU鏈表中釋放。如果沒有找到再從lru鏈表中淘汰。
  • 如果沒有找到則嘗試從lru中flush dirty page并加入到free list中。
  • 沒有找到,設置scan_all重復上述過程,與第一遍不同的地方在于需要scan整個lru鏈表。
  • 如果遍歷了整個lru鏈表依然沒有找到可以淘汰的block,則sleep 10s等待page cleaner線程做一批淘汰或者刷臟。
  • 重復上述過程直到找到一個空閑block。超過20遍設置warn信息。
  • page cleaner

    系統線程,負責定期清理空閑頁放入free list中和flush dirty page到磁盤上,dirty page指那些已經被修改但是還未寫到磁盤上的數據頁。使用innodb_page_cleaners 參數設定page cleaner的線程數,默認是4。通過特定參數控制dirty page所占buffer pool的空間比維持在一定水位下,默認是10%。

    flush list

    上面章節提到過flush_list是包含dirty page并按修改時間有序的鏈表,在刷臟時選擇從鏈表的尾部進行遍歷淘汰,代碼主體在buf_do_flush_list_batch中。這里不得不提的一個巧妙的操作,叫作Hazard Pointer,buf_pool_t中的flush_hp,將整體遍歷復雜度由最差O(n*n)降到了O(n)。之所以復雜度最差會變為O(n*n)是由于flush list允許多個線程并發刷臟,每次從鏈表尾部進行遍歷,使用異步io的方式刷盤,在io完成后將page從鏈表中摘除,每次提交異步io后從鏈表尾部再次掃描,在刷盤速度比較慢的情況下,可能每次都需要跳過之前已經flush過的page,最差會退化為O(n*n)。

    flush_hp:

    用于刷臟遍歷flush list過程,flush_list_mutex保護下修改,在處理當前page之前,將hazard pointer設置為下一個要遍歷的buf_page_t的指針,為線程指定下一個需要處理的page。當前page刷盤時會釋放flush_list_mutex,刷盤完成后重新獲得鎖,處理flush_hp指向的page,無論這中間發生過什么,鏈表如何變動,flush_hp總是被設置成為下一個有效的buf_page_t指針。所以復雜度總能保證為O(n)。

    刷臟的過程中也做了一些優化,代碼在buf_flush_page_and_try_neighbors中,可以將當前page相鄰的dirty page頁也一起刷盤,目的是將多個隨機io轉為順序io減少overhead,這在傳統HHD的設備上比較有用,在SSD上seek time已經不是顯著的影響因素。可以使用參數innodb_flush_neighbors進行設置和關閉:

  • 為0則關閉flush neighbors的優化
  • 默認值為1,flush與當前page相同extent(1M)上的連續dirty page
  • 為2則flush與當前page相同extent上的dirty page
  • flush LRU list

    buf_flush_LRU_list主要完成兩件事:

  • 將lru list尾部的可以移除的pages放入到free list中
  • 將lru list尾部的dirty page刷到磁盤。
  • 同一時刻只會有一個page cleaner線程對同一個LRU list操作。lru list遍歷的深度由動態參數innodb_lru_scan_depth決定,用于優化io敏感的場景,默認值1024,設置比默認值小的值可以適應大多數work load,設置過大會影響性能,尤其是在buffer pool足夠大的情況。操作過程在LRU_list_mutex的保護下,代碼主體在buf_do_LRU_batch中。其中涉及到unzip_LRU list和LRU list中的淘汰,unzip_LRU list不一定每次都會做淘汰操作,衡量內存大小和負載情況,只有在size超出buffer pool的1/10以及當前負載為io bound的情況才會做。代碼主體在buf_free_from_unzip_LRU_list_batch中, 將uncompressed page移除到free list中,并不會將任何數據刷盤,只是將解壓縮的frames與壓縮page分離。

    如果在上述操作后仍無法達到需要釋放的page數量(遍歷深度),則繼續從lru list尾部進行遍歷,操作在buf_flush_LRU_list_batch中,lru list的遍歷同樣使用了Hazard Pointer,buf_pool_t中的lru_hp。當前page如果是clear并且沒有被io fixed和buffer fixed,則從lru list中移除并加入free list中,否則如果page是已經修改過的并且滿足flush的條件則對其進行刷臟。

    小結

    innodb設定了buffer pool的總大小,空閑page不夠用時會將lru鏈表中可替換的頁面移到free list中,根據統計信息估計負載情況來決定淘汰的策略。所有的block在幾種狀態之間進行轉換,unzip_LRU、flush list設置一定的上限,設置多個影響淘汰和刷臟策略的參數,以達到不同負載不同buffer pool size下的性能和內存之間的平衡。

    change buffer

    change buffer是用于緩存不在buffer pool中的二級索引頁改動的數據結構,insert、update或者delete這些DML操作會引起buffer的改變,page被讀入時與change buffer中的修改合并加入buffer pool中。引入buffer pool的目的主要減少隨機io,對于二級索引的更新經常是比較隨機的,當頁面不在buffer pool中時將其對應的修改緩存在change buffer中可有效地減少磁盤的隨機訪問。可以通過參數 innodb_change_buffering 設置對應緩存的操作:all, none, inserts, deletes, changes(inserts+deletes), purges。change buffer的內存也是buffer pool中的一部分,可以通過參數innodb_change_buffer_max_size來設置內存占比,默認25%,最多50%。

    Adaptive Hash Index

    innodb的索引組織結構為btree,當查詢的時候會根據條件一直索引到葉子節點,為了減少尋路的開銷,AHI使用索引鍵的前綴建立了一個哈希索引表,在實現上就是多個個hash_tables(分片)。哈希索引是為那些頻繁被訪問的索引頁而建立的,可以理解為btree上的索引。看代碼初始創建的數組大小為buf_pool_get_curr_size() / sizeof(void *) / 64,其實是比較小的一塊內存,使用malloc分配。

    log buffer

    log buffer是日志未寫到磁盤的緩存,大小由參數innodb_log_buffer_size指定,一般來說這塊內存都比較小,默認是16M。在有大事務的場景下,在事務未commited之前可以將redo日志數據一直緩存,避免多次寫磁盤,可以將log buffer調大。

    參考資料:

    https://dev.mysql.com/doc/refman/5.6/en/innodb-performance-use_sys_malloc.html

    https://dev.mysql.com/doc/refman/8.0/en/innodb-in-memory-structures.html

    總結

    以上是生活随笔為你收集整理的系统无法分配所需内存_Innodb内存管理解析的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。