標籤:
本人原創文章,轉載請註明出處。
mysql的記憶體管理龐大而先進,這在mem0pool.c檔案的開頭注釋中都有說明,粗略的可以分成四部分,包含9大塊:
buffer pool,
parsed andoptimized SQL statements,
data dictionarycache,
log buffer,
locks for eachtransaction,
hash table forthe adaptive index,
state andbuffers for each SQL query currently being executed,
session foreach user, and
stack for eachOS thread.
9大塊通過4部分進行管理
A solution tothe memory management:
1. the bufferpool size is set separately;
2. log buffersize is set separately;
3. the commonpool size for all the other entries, except 8, is set separately.
也就是緩衝池,redo日誌緩衝,普通池和8(使用者session資訊,可看做一部分)
redo日誌緩衝由redo部分單獨管理,bufferpool也就是緩衝池是一個複雜的部分,內容很多,普通池上面說了,除了8,和1,2.其餘的都歸它管。上面這個結構就是mysql記憶體子系統的完整圖景。:
本篇從緩衝池(buf pool)講起,然後會講解common pool和插入緩衝。
在做完整、全面而詳細的緩衝池構架分析之前,必須先要窮舉所涉及到的全部bufpool子系統組件和重點,內容非常之多。
(1)Buf pool五大組成模組
首先,bufpool子系統可以分成最基本的五個模組組件。
緩衝池常式常規管理(buffer pool routines),進行buf pool(多)執行個體管理,協調另外四模組進行運行,對儲存、事務、redo日誌、系統主線程等外部子系統等提供調用函數介面,是五個模組的核心。
LRU鏈表管理(LRU replacement algorithm),buf pool中基礎控制儲存單元(下文即將詳細介紹)如buf_block_struct和buf_page_struct,都需要在對應的非壓縮與壓縮LRU鏈表中進行管理,從設計思想上需要實現加入LRU鏈表節點、刪除LRU鏈表節點兩個基本點,進而需要實現分配塊、釋放塊兩個進階功能,由此衍生出15個可以被外部模組調用的重要主函數;另外,雖然不為外部介面調用,但與主函數關聯的仍舊有14個重要輔助函數(主函數的全部實現與嵌套細節),這兩部分相當於LRU全部43個函數中的60%以上。
重新整理機制(flush algorithm),bufpool部分與底層IO互動最緊密的模組,直接完成重新整理動作,提供的各種重新整理介面分別被子系統之內的所有模組調用。
用重新整理方式可以分為:通過LRU鏈表重新整理的方式(BUF_FLUSH_LRU)和通過flush鏈表重新整理的方式(BUF_FLUSH_LIST)。從功能來說,flush模組也必然包含flush鏈表的管理功能,實現鏈表節點的插入,刪除,進而實現單塊重新整理、臨近頁重新整理、doublewrite組件重新整理這三個主要功能,並實現外部介面,供事務部分(和mini事務部分)commit動作中實現flush鏈表髒塊的插入。
夥伴系統(Binary buddy allocator for compressed pages),在bufpool裡面,夥伴系統並不是實現統一記憶體配置管理的單元,它的作用僅僅限於分配buf控制塊(buf_page_struct)所需壓縮頁記憶體配置動作,但仍具有不可小視的巨大作用。Bufpool最終的目標是為實現各種供檔案儲存體系統、交易管理、主進程調度單元等使用的外部介面,而這些部分基本都需要或多或少的對buf控制塊中所管理的頁中的資料記錄(buf_block_struct->buf_page_struct.zip.data)進行讀寫操作,在夥伴管理分配記憶體之後,底層還要根據系統運行需要,進行壓縮頁到非壓縮頁的轉換,可以說是極端重要的一個組件。
讀緩衝(buffer read),實現的功能簡單直接,單頁非同步讀、隨機讀、線性預讀,運行這些功能的同時也存在著根據不同情況,進行的LRU鏈表(LRU和unzip_LRU)對讀取到的頁的控制塊(buf_block_struct或buf_page_struct)進行節點插入、刪除、置於鏈表末尾等操作,有LRU操作的地方自然也可能存在flush鏈表的重新整理動作。
(2)四個重要結構體
其次,在子系統五大組件啟動並執行過程中,涉及到四個重要的結構體,
緩衝池控制執行個體(buf_pool_struct),實現bufpool執行個體管理的核心資料結構,包含上文提到下文會詳細描述的6大鏈表表頭(UT_LIST_BASE_NODE_T),包含非同步IO重新整理條件變數(os_event_t)、LRU和flush鏈表互斥體、壓縮與非壓縮頁hash表,上面這些僅僅是最重要部分。
struct buf_pool_struct{
。。。。。。
ulint LRU_old_ratio;
mutex_t LRU_list_mutex;
rw_lock_t page_hash_latch;
mutex_t free_list_mutex;
。。。。。。
hash_table_t* page_hash;
。。。。。。
UT_LIST_BASE_NODE_T(buf_page_t)flush_list;
。。。。。。
UT_LIST_BASE_NODE_T(buf_page_t)free;
。。。。。。
UT_LIST_BASE_NODE_T(buf_page_t)LRU;
。。。。。。
};//結構體的內容難以一時盡述,下文會在每個模組中需要用到的部分時逐步引用並逐一加以解釋。
底層記憶體配置單元(buf_chunk_struct),在無數的5.5以上版本mysql核心分析文章中,這個結構體都是最容易被人忽視的部分,但從某個意義上說是極其重要的,因為它最貼近bufpool的底層—OS記憶體配置,所有的buf控制塊(buf_block_struct),都是掛載到6大鏈表中並可以直接通過的(buf_pool_struct)進行管理,但是這些結構體最初的記憶體配置動作,都是在(buf_chunk_struct)結構體的初始化階段完成的,(buf_chunk_struct)又是(buf_pool_struct)的最基礎最底層的記憶體配置單元。雖然這部分在代碼量級上可說是“無足輕重”,但就重要性來講絕對不能無視。
struct buf_chunk_struct{
ulint mem_size; /*!< allocated size of the chunk */
ulint size; /*!< size of frames[] and blocks[] */
void* mem; /*!<pointer to the memory area which
wasallocated for the frames */
buf_block_t* blocks; /*!<array of buffer control blocks */
};//chunk結構體內容較少,此處已經列舉全部內容,下文會根據模組引用做說明。
非壓縮頁控制塊(buf_block_struct),在網易老薑所著的《mysql核心 innodb儲存引擎卷1》中對此結構體做過描述,隨著版本的變遷和mysql功能的遞進,頁控制塊也進化了,非壓縮頁控制塊(buf_block_struct)的管理與壓縮頁控制塊(buf_page_struct)單獨分開,前者包含後者的結構體引用、物理頁幀地址、unzip_LRU鏈表節點(UT_LIST_NODE_T)、讀寫鎖、互斥體等重要對象,是實現bufpool各種核心介面功能的最關鍵控制單元。
struct buf_block_struct{
buf_page_t page; /*!<page information; this must
bethe first field, so that
buf_pool->page_hashcan point
tobuf_page_t or buf_block_t */
byte* frame; /*!< pointer to buffer frame which
isof size UNIV_PAGE_SIZE, and
alignedto an address divisible by
UNIV_PAGE_SIZE*/
。。。。。。
};//本結構體中其他的部分都先不做任何的列舉,但請一定記住上面這兩個,page非壓縮頁結構體引用壓縮頁結構體的重要控制代碼,同時也是在一個重要函數中進行強制轉換操作((buf_block_t*) bpage)最關鍵的部分!至於frame則是bufpool部門真正服務的核心所在,這是非壓縮頁(資料頁,undo頁,特殊頁。。。。。。)的頁幀地址。一旦某頁記錄讀入通過read模組讀入鏈表進行管理之後,那麼它的所有modify操作等同雩都是針對這個頁幀裡面做記憶體修改,至於寫回磁碟是非同步(同步)IO需要考慮的事情(詳解檔案儲存體子系統的時候會對IO機製作出完整說明)。
壓縮頁控制塊(buf_page_struct),理論上講,全部的非壓縮頁只是壓縮頁的子集(實際情況有待本人進一步驗證),因為在進行核心操作的時候,都是在非壓縮頁中進行,因此壓縮頁控制塊(buf_page_struct)並不包含互斥體,但是為了保證與(buf_block_struct)的一致,需要進行鎖計數器的實現,另外,它還包含對應物理頁的資料表空間id、資料表空間內頁的位移量、頁狀態、重新整理類型(上文介紹過BUF_FLUSH_LRU和BUF_FLUSH_LIST)、壓縮頁對應引用、所在hash表、5大鏈表(unzip_LRU在非壓縮頁結構體對象(buf_block_struct)所以此處才是剩下的5個鏈表)的子節點(UT_LIST_NODE_T)、以及是否處於OLD_LRU端(LRU鏈表結構old部分)是否崩潰頁初次訪問時間等細節,這些也僅僅是列舉出的最重要結構體對象而已,不是全部。
struct buf_page_struct{
unsigned space:32; /*!<tablespace id; also protected
bybuf_pool->mutex. */
unsigned offset:32; /*!<page number; also protected
bybuf_pool->mutex. */
。。。。。。
unsigned flush_type:2; /*!< if this block is currently being
flushedto disk, this tells the
flush_type.
@seeenum buf_flush */
unsigned io_fix:2; /*!<type of pending I/O operation;
alsoprotected by buf_pool->mutex
@seeenum buf_io_fix */
unsigned buf_fix_count:19;/*!< count of howmanyfold this block
iscurrently bufferfixed */
。。。。。。
UT_LIST_NODE_T(buf_page_t)free;
UT_LIST_NODE_T(buf_page_t)flush_list;
UT_LIST_NODE_T(buf_page_t)zip_list;
。。。。。。
};
(3)六個重要鏈表
再次,子系統組件與基礎控制結構體運作的過程要實現各種複雜功能,必然無法脫離6個重要的鏈表,
空閑鏈表(buf_pool->free),bufpool最重要的三個鏈表之一,上文已經多次提到LRU和flush等鏈表,但未提到空閑鏈表,實際上它是在系統初始化階段bufpool進行初始化後唯一顯式調用鏈表初始化函數進行init操作的唯一bufpool鏈表。系統內的第一個LRU鏈表塊,必然是從free鏈表中擷取到的,當flush模組髒頁重新整理完成,LRU鏈表節點就會被清除或者移動到LRU鏈表結尾等待清除,清除LRU之後的節點仍舊是迴歸到free鏈表內。
LRU鏈表(buf_pool->LRU),bufpool最重要的三個鏈表之二,當有LRU鏈表為空白時,必然從free鏈表擷取空閑節點,並進行非同步IO讀將頁讀入bufpool,並加入LRU鏈表,LRU鏈表長度過大的情況下,會進行尾部重新整理,重新整理失敗會進行更徹底的直接通過LRU進行髒頁重新整理(BUF_FLUSH_LRU方式),flush鏈表節點得到釋放髒頁完成重新整理,並同時把LRU鏈表的髒塊也完成移除。可以說LRU和free、flush三個鏈表、5個模組有之間有著千絲萬縷的聯絡。
非壓縮LRU鏈表(buf_pool->unzip_LRU),本鏈表實際是LRU鏈表的一個子集,在壓縮頁控制塊(buf_page_struct)中的壓縮頁需要進行解壓縮以進行各種記錄級讀寫操作時,該鏈表將發揮作用,因此可以說,插入到了unzip_LRU鏈表就一定頁在LRU鏈表中,反之則未必。
髒塊鏈表(buf_pool->flush_list),bufpool最重要的三個鏈表之二,實際上也是LRU鏈表的子集,所以讀入bufpool的頁都通過壓縮的或者非壓縮的控制塊進行管理,最初一定是在LRU鏈表中,當事務部分(和mini事務部分)完成commit操作時候,實際上就意味著記憶體寫的成功,髒頁必然要加入flush鏈表,並等待非同步IO線程(系統主線程子系統組成之一)進行重新整理操作(linux原生非同步IO和作者Heikki Tuuri自己用條件變數實現的類比非同步IO,buf_pool_struct提到過,儲存部分會做詳細說明)動作。
未修改壓縮塊鏈表(buf_pool->zip_clean),本鏈表以目前的原始碼來看僅用於調試功能。
夥伴系統空閑鏈表(buf_pool->zip_free[]),bufpool6大鏈表中最特殊的一個,鏈表的根節點可以看做“是一個指標數組”,夥伴系統的精髓就在於按照2的倍數進行緊鄰記憶體塊的合并和拆分,進而達到高效管理、代碼複雜度低的效果。這個指標數組按照塊大小實際包含4層,1024,2048,4096和8192,每一層基結點只管理同類大小的塊。
關於bufpool部分的整體概述,暫時告一段落,後續還會陸續添加新的內容,並作進一步整理。老劉會堅持把這個分析一直做下去。如有興趣的朋友也可以QQ加本人私聊:275787374,隨時歡迎。
下一部分會將LRU進行詳解。如發現文章中的任何錯誤歡迎朋友們指正。
原創mysql核心原始碼深度解析 緩衝池 buffer pool 整體概述