[MySQL 源码] 从buffer pool中获取空闲block流程
以下分析基于Percona Server 5.5.18
buf_LRU_get_free_block loop: 1.block = buf_LRU_get_free_only(buf_pool) 首先從buf_pool->free鏈表尾部讀取,如果有空閑頁,則將其從buf_pool->free中移除,設(shè)置bpage->state=BUF_BLOCK_READY_FOR_USE,然后返回 上述流程需要加buf_pool->free_list_mutex鎖
2.如果1獲得了一個block,還需要重置壓縮頁描述符block->page.zip為0,然后直接返回
3.如果在buf_pool->free上沒有block,則從buf_pool->LRU或unzip_LRU的尾部開始掃描,嘗試找一個空閑block. freed = buf_LRU_search_and_free_block(buf_pool, n_iterations); //n_iterations是一個計數(shù)器,表示嘗試釋放但失敗的次數(shù)。 A.持有buf_pool->LRU_list_mutex鎖 B.首先,嘗試從buf_pool->unzip_LRU上釋放block,這種情況下不會釋放壓縮頁數(shù)據(jù) freed = buf_LRU_free_from_unzip_LRU_list(buf_pool, n_iterations, have_LRU_mutex);
>>判斷是否從unzip_LRU上驅(qū)逐block
理論上講,從buf_pool->unzip_LRU上應(yīng)該更容易獲得一個block,因為我們可以選擇一個臟塊(只驅(qū)逐解壓頁),但當(dāng)我們嘗試5次還是沒有找到時,則直接返回到正常的驅(qū)逐block的邏輯,即從LRU上獲取
另外也有函數(shù)buf_LRU_evict_from_unzip_LRU用來判斷是否從unzip_LRU上驅(qū)逐block,其判斷邏輯如下:
>>>需要持有buf_pool->LRU_list_mutex
>>>如果buf_pool->unzip_LRU長度為0 ,返回FALSE
>>>如果buf_pool->unzip_LRU小于buf_pool->LRU的十分之一,返回FALSE
>>>如果buf_pool->freed_page_clock == 0,表示之前沒有進(jìn)行過任何block驅(qū)逐,默認(rèn)假設(shè)工作負(fù)載為disk bound,返回TRUE
freed_page_clock是一個序列號,用來計數(shù)從LRU尾部移除的block數(shù),可以不加鎖讀取該變量
>>>計算最近的平均IO量
? ? io_avg = buf_LRU_stat_sum.io / BUF_LRU_STAT_N_INTERVAL
? ? ? ? + buf_LRU_stat_cur.io;
最近50秒的平均IO+當(dāng)前的IO,獲得最近的平均IO負(fù)載
? ? unzip_avg = buf_LRU_stat_sum.unzip / BUF_LRU_STAT_N_INTERVAL
? ? ? ? + buf_LRU_stat_cur.unzip;
最近50秒平均解壓page的次數(shù)+當(dāng)前解壓page的次數(shù),獲得最近每秒平均解壓page次數(shù)
我們對io_avg進(jìn)行加權(quán)(BUF_LRU_IO_TO_UNZIP_FACTOR),依次判斷是IO-BOUND還是CPU-BOUND
當(dāng)unzip_avg <= io_avg * BUF_LRU_IO_TO_UNZIP_FACTOR時,表示這是IO-Bound的,返回TRUE
當(dāng)unzip_avg ?> ?io_avg * BUF_LRU_IO_TO_UNZIP_FACTOR時,表示這是CPU-Bound的,返回FALSE。
BUF_LRU_IO_TO_UNZIP_FACTOR是一個宏,值為50。這是一個hard code值,但正如bug#64181所提到的,快速存儲例如SSD可能并不適合這樣的加權(quán),因為SSD相對傳統(tǒng)硬盤具有更快的IO速度。
當(dāng)從buf_LRU_evict_from_unzip_LRU返回值為false時,則從LRU掃描,如果為true,則嘗試從unzip_lru掃描
>>計算在unzip中的最大掃描距離
? ? distance = 100 + (n_iterations
? ? ? ? ? ? ? * UT_LIST_GET_LEN(buf_pool->unzip_LRU)) / 5
? ? ? ? ? ? ? ? ? ? 其中n_iterations<5
>>從buf_pool->unzip_LRU尾部開始掃描,滿足如下條件可以進(jìn)行驅(qū)逐
buf_block_get_state(block) == BUF_BLOCK_FILE_PAGE;
block->in_unzip_LRU_list;
block->page.in_LRU_list;
然后從unzip_lru上釋放block:
freed = buf_LRU_free_block(&block->page, FALSE, have_LRU_mutex);
>>>檢查block是否被pin住(buffer-fixed or I/O-fixed),是的話直接返回false
>>>如果表正在被刪除(bpage->space_was_being_deleted) 并且bpage->oldest_modification !=0時調(diào)用buf_flush_remove(bpage)從flush list上刪除該block
其中bpage->oldest_modification記錄了修改該block,但尚未刷入磁盤的日志記錄起始LSN。值為0表示所有的修改都刷入了磁盤。
>>>分配一個page描述符(buf_page_t)
b = buf_page_alloc_descriptor();
然后將bpage拷貝到b中
?memcpy(b, bpage, sizeof *b);
這里bpage->zip.data的指針也被拷貝到b,因此可以通過b訪問壓縮頁,而在隨后將bpage->zip.data置為NULL
>>>從LRU和Page hash上移除page
buf_LRU_block_remove_hashed_page(bpage, zip)
當(dāng)前流程的zip為false,表示不釋放壓縮page
|–>buf_LRU_remove_block(bpage);
|–>從buf_pool->page_hash中移除
這里zip參數(shù)為false,因此無需釋放壓縮頁數(shù)據(jù)。但如果從LRU釋放,這里可能會成為瓶頸(buf_buddy_free做碎片整理)
>>>將b插入到buf_pool->page_hash和buf_pool->LRU中。
>>>移除可能存在的adaptive hash index記錄
btr_search_drop_page_hash_index((buf_block_t*) bpage, NULL);
>>>計算b->zip.data的checksum,并寫入壓縮頁。
>>>將空出來的bpage加入到buf_pool->free上
? ? ?buf_LRU_block_free_hashed_page((buf_block_t*) bpage, FALSE);
C.如果buf_pool->unzip_LRU上找不到空閑塊,這時候會去從buf_pool->LRU上獲取,這種情況下,如果驅(qū)逐的是壓縮表的block,還會釋放壓縮頁 ? ? if (!freed) { ? ? ? ? freed = buf_LRU_free_from_common_LRU_list( ? ? ? ? ? ? buf_pool, n_iterations, have_LRU_mutex); ? ? }? 該函數(shù)會從buf_pool->LRU的尾部開始掃描,在沒有壓縮表的情況下這也是普遍調(diào)用的函數(shù) 函數(shù)freed = buf_LRU_free_block(bpage, TRUE, have_LRU_mutex)會被調(diào)用到去從LRU上釋放該塊,注意這里第二個參數(shù)為TRUE,這表明會同時釋放壓縮頁,并做碎片整理工作。 buf_LRU_free_block->buf_LRU_block_remove_hashed_page->buf_buddy_free->buf_buddy_free_low,另外buf_LRU_block_remove_hashed_page中還會釋放bpage的page描述符(buf_page_free_descriptor)
related bug:http://bugs.mysql.com/bug.php?id=64344
如bug#64344所提到的,malloc/free是在持有buffer pool鎖的情況下進(jìn)行的,這會對并發(fā)操作產(chǎn)生開銷。 D.更新buf_pool->LRU_flush_ended計數(shù)并釋放buf_pool->LRU_list_mutex ? ? if (!freed) { ? ? ? ? buf_pool->LRU_flush_ended = 0; ? ? } else if (buf_pool->LRU_flush_ended > 0) { ? ? ? ? buf_pool->LRU_flush_ended–; ? ? } 4.從步驟3成功釋放了一個空閑block,則goto loop5.n_iterations > 30時,開始打印警告和監(jiān)控信息到err log中
6.如果走到這一步,表明找不到空閑塊,嘗試刷LRU, 并喚醒AIO線程 buf_flush_free_margin(buf_pool, TRUE);
7.如果buf_pool->LRU_flush_ended > 0,表明我們已經(jīng)在一次LRU Flush中寫入了page,為了讓insert? buffer更高效,將這些page轉(zhuǎn)移到free list上(翻譯自注釋) buf_LRU_try_free_flushed_blocks(buf_pool);
8.n_iterations > 10時os_thread_sleep(500000)
9.n_iterations++; goto loop
總結(jié)
以上是生活随笔為你收集整理的[MySQL 源码] 从buffer pool中获取空闲block流程的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 如何写出《黄焖鸡米饭是怎么火起来的》这样
- 下一篇: 文件存放问题