Buffer pool instance對應(yīng)的結(jié)構(gòu)體是buf_pool_t。整個buffer pool由多個instance組成,個數(shù)等于innodb_buffer_pool_size/innodb_buffer_pool_instances。instances是為并發(fā)讀取與寫入而設(shè)計,各instance之間沒有鎖競爭關(guān)系。當(dāng) innodb_buffer_pool_size小于1GB時為防止instances太小而出現(xiàn)性能問題,innodb_buffer_pool_instances會被重置為1。instance之內(nèi)包含完整鎖、信號量、chunks、為方便數(shù)據(jù)頁管理而設(shè)計的邏輯鏈表(lru list、free list、flush list)以及一個用于快速找到指定space_id和page_no的數(shù)據(jù)頁的的page hash鏈表。
Buffer pool chunk
Buffer pool chunk對應(yīng)的結(jié)構(gòu)體是buf_chunk_t。每個buffer pool instance被均勻劃分為多個chunk,buffer pool resize以chunk為粒度。chunk分為數(shù)據(jù)頁和數(shù)據(jù)頁對應(yīng)的控制體,控制體中有指針指向數(shù)據(jù)頁。遍歷所有instance的chunk可以幾乎訪問innodb所有緩存的數(shù)據(jù),只有部分諸如尚未解壓的壓縮頁等除外。
Buffer pool page
Buffer pool page對應(yīng)的結(jié)構(gòu)體包括塊描述符buf_block_t和頁面描述符buf_page_t。Buffer pool page大小為16KB,默認(rèn)與磁盤上數(shù)據(jù)頁的大小相同(innodb的文件結(jié)構(gòu)的介紹,請參考淺析InnoDB文件結(jié)構(gòu)),其緩存的不僅僅是數(shù)據(jù)頁,緩存的對象還包括:undo log頁面、change buffer(插入緩存信息)、AHI(自適應(yīng)哈希)、SDI(結(jié)構(gòu)化字典信息)、行鎖等。所有緩存對象在buffer pool中都是以頁面為單位存儲。
Innodb支持?jǐn)?shù)據(jù)頁壓縮,壓縮頁的大小在建表的時候指定,目前支持的范圍包括16K,8K,4K,2K,1K等(由于壓縮頁的管理方式與普通頁面不同,即使指定16K的壓縮頁,也能對數(shù)據(jù)量大的類型有一定益處)。壓縮頁面在buffer pool中使用伙伴系統(tǒng)管理,不論壓縮頁面在磁盤上的大小是多少,解壓后都為16K。當(dāng)buffer pool空閑頁面不足時,innodb會優(yōu)先淘汰壓縮頁面的解壓頁(buf_LRU_free_from_unzip_LRU_list),當(dāng)前者操作后仍不能為innodb提供足夠的空閑頁面時,會接著淘汰LRU list上的正常頁面和壓縮頁面(buf_LRU_free_from_common_LRU_list)。 1.1.1 Buffer pool page結(jié)構(gòu)
每個buffer pool page的buf_block_t和frame等信息于buf_chunk_init函數(shù)中被初始化。在使用allocate_chunk分配到chunk的空間后,chunk內(nèi)所有頁面的buf_block_t連在一起從內(nèi)存前向后初始化,chunk內(nèi)所有frame連在一起,從內(nèi)存后向前初始化,最左側(cè)的buf_block_t控制最左側(cè)的frame,如下圖所示:
伙伴系統(tǒng)調(diào)用buf_buddy_alloc釋放空間。對于size為16K的空間回收,則直接清空后掛回到16K鏈表即可。對于size低于16K的空間回收,會先遞歸尋找其伙伴空間,如果伙伴空間也處于空閑狀態(tài),則合并至更大的size;如果合并失敗,則將釋放的空間掛至zip free對應(yīng)鏈表中。通過這樣的方式,盡可能減少伙伴系統(tǒng)中的內(nèi)存碎片。 1.2.9 Withdraw list
withdraw list僅僅用于buffer pool縮小過程(bp resize),對應(yīng)的結(jié)構(gòu)為buf_pool_t中的UT_LIST_BASE_NODE_T(buf_page_t) withdraw。下面介紹buffer pool resize的過程:
用戶設(shè)置innodb_buffer_pool_size參數(shù)會觸發(fā)buffer pool size的更新函數(shù)innodb_buffer_pool_size_update,該函數(shù)設(shè)置srv_buf_resize_event,借此方式通知buffer pool更新事件。
專門用于處理buffer pool resize的后臺線程buf_resize_thread接到srv_buf_resize_event事件,調(diào)用buf_pool_resize開啟resize實際過程。
如果開啟adaptive hash index(AHI),則暫時關(guān)閉并清空。
如果目標(biāo)空間小于當(dāng)前空間,則計算withdraw_target。當(dāng)前buffer pool instance的最后部分多余的chunks將被回收。接下來借用withdraw list匯總收縮空間的頁面(buf_pool_withdraw_blocks),buf_pool_withdraw_blocks詳細邏輯如下:
如果新的buffer pool size是舊size的2倍或者不到1/2,將重新設(shè)置page hash和zip hash(buf_pool_resize_hash)。
釋放buffer pool所有鎖,允許并發(fā)負載。
如果新的buffer pool size是舊size的2倍或者不到1/2,進行l(wèi)ock_sys_resize和dict_resize,重新設(shè)置lock_sys->rec_hash、lock_sys->prdt_hash、lock_sys->prdt_page_hash、dict_sys->table_hash和dict_sys→table_id_hash等結(jié)構(gòu)的大小。
逐一掃描每個所有buffer pool instance的LRU list和CLOCK list中的每個頁面,將頁面的space_id和page_no匯總到以space_id為key,unordered_set為value的lru_maps中。掃描結(jié)束后,所有頁面按照space_id進行了初步歸類。
對某個表執(zhí)行flush table for export操作時,會調(diào)用ibuf_merge_space強制合并change buffer(row_quiesce_table_start)。合并change buffer是為flush for export準(zhǔn)備的,避免準(zhǔn)備拷貝的ibd文件對change buffer有依賴。
2.5 Adaptive hash index優(yōu)化
2.5.1 原理
innodb的數(shù)據(jù)存儲于B+樹中,B+樹通常不會太高,通常只有3到5層。從根節(jié)點到葉節(jié)點的尋路涉及到多層頁面內(nèi)記錄的比較,即使所有路徑上的頁面都在內(nèi)存中,也是比較消耗CPU的操作。
對尋路到CPU開銷優(yōu)化分兩部分,第一部分為盡可能避免尋路次數(shù),innodb為此設(shè)計了多個優(yōu)化,例如一次尋路多緩存幾條記錄到fetch_cache中;尋路結(jié)束將cursor緩存到row_prebuilt_t::pcur,方便下次查詢復(fù)用。第二部分為盡可能避免單詞尋路的開銷。Adaptive hash index(AHI)便是為此而設(shè)計。AHI可以理解為建立在B+樹上的索引,它采用自適應(yīng)的方式管理,為B+樹尋路建立以查詢條件為key,B+樹record地址為value的hash index。
AHI的大小為buffer pool size的1/64,在buf_pool_init中調(diào)用btr_search_sys_create初始化。為了避免AHI的鎖競爭壓力,innodb支持AHI分區(qū),可以使用innodb_adaptive_hash_index_parts參數(shù)配置分區(qū)個數(shù),默認(rèn)為8。當(dāng)buffer pool size動態(tài)調(diào)整大小為原大小的2倍以上或者1/2以下時,AHI也隨之調(diào)用btr_search_sys_resize調(diào)整大?。╞uf_pool_resize)。 2.5.2 創(chuàng)建過程