innoDB原始碼分析 緩衝池

2021-09-09 01:57:32 字數 4274 閱讀 8665

最開始學oracle的時候,有個概念叫sga和pga,是非常重要的概念,其實就是記憶體中的緩衝池。innodb的設計類似於oracle,也會在記憶體中開闢一片緩衝池。眾所周知,cpu的速度和磁碟的io速度相差可以用鴻溝來形容,因此聰明的前輩們使用了記憶體這個rom來彌補這道鴻溝,那麼資料庫的設計者們也繼承了這個優良的設計理念,在記憶體中開闢了一片區域,存放緩衝資料,提高資料庫效率。

可以將磁碟的緩衝區理解成乙個簡單的模型--由資料塊組成的一片區域,資料塊(block/page)預設大小是16kb。那麼現在可以畫出乙個好理解的模型出來了:

這裡的每乙個格仔都代表乙個page。在**裡這個區域有兩個關鍵的資料結構:buf_pool_struct和buf_block_struct。其中buf_pool_struct是緩衝池的資料結構,buf_block_struct是資料塊的資料結構。

對於緩衝池的管理,innodb維護了乙個free鍊錶,該鍊錶中記錄了沒有被使用的記憶體塊,每次申請資料塊都是要從free鍊錶中取。但是,一般來說資料庫的緩衝池都會比實際資料量小,因此緩衝池總有用完的一天,也就是說free鍊錶的所有頁都被分配完了,這個時候另乙個資料結構開始發揮作用--lru鍊錶。

lru是乙個經典的演算法,全稱是最近最少使用(lastest least used)。使用最頻繁的頁總是在鍊錶的前面,而最後的頁就是要被釋放掉的頁。然而innodb沒有採用這種大路貨,而是另闢蹊徑的搞了個改進版的lru,有人管他叫做midpoint lru,是這樣的:

innodb的主要改進點在於每次將磁碟上讀出的資料不是直接放到鍊錶的頭部,而是放在鍊錶的3/8處(該值可配置),只有在下次訪問該頁時,才會將該頁移動到鍊錶頭部。這樣改進的原因在《mysql核心--innodb儲存引擎》一書中有論述(p250)。這個鍊錶就被分為了兩部分,midpoint前叫做young list,midpoint後叫做old list。鍊錶尾部的資料塊會被釋放掉,buf_lru_search_and_free_block函式會完成這個操作:

block = ut_list_get_last(buf_pool->lru);

while (block !=null)

上面**片段裡體現了上面說的釋放過程。

之前說的所有都是建立在乙個假設上--free鍊錶中的頁分配完。那麼資料庫剛啟動的時候,free鍊錶有充足的頁可以去分配,innodb是如何運作的呢?

buf_lru_add_block函式的注釋中明確寫道,該函式用於將block加入lru list中。因此任何將block加入lru的操作都是該函式完成的,無論free鍊錶是否還有頁可以被分配。在檢視這個函式的時候我注意到了乙個常量:buf_lru_old_min_len。在5.1.73的**裡它被設定成80。該函式會判斷block的young標記,在系統初始化時,這個函式會將所有的block置為young,並放在鍊錶頭部,直到lru鍊錶的長度大於等於buf_lru_old_min_len。

在lru長度大於等於buf_lru_old_min_len之後,innodb會將lru中所有的頁置為old(buf_lru_old_init),然後呼叫buf_lru_old_adjust_len函式去調整位置,直到鍊錶呈現上面的狀態。下面是**:

void

buf_lru_old_adjust_len(

void)/*

********************====*/

else

if (old_len > new_len +buf_lru_old_tolerance)

else}}

可以看出來,函式採用了乙個無條件迴圈不停地移動buf_pool->lru_old的位置,直到滿足了條件。

至於lru鍊錶的插入操作,其實很簡單,就是每次將新插入的頁放置到buf_pool->lru_old的next位置,以後再次訪問該資料頁的時候,呼叫buf_lru_make_block_young函式將其移動到鍊錶的頭部。

ut_list_insert_after(lru, buf_pool->lru, buf_pool->lru_old,

block);

ut_list_insert_after的注釋裡寫的很明白:inserts a node2 after node1 in a list. 這裡的node1是指buf_pool->lru_old,node2是指block。而buf_lru_make_block_young函式中關鍵的一步:

ut_list_add_first(lru, buf_pool->lru, block);
ut_list_add_first的注釋裡這麼寫道:adds the node as the first element in a two-way linked list.

至此基本上了解了乙個資料頁是如何被讀取到記憶體中的。總結一下,從啟動開始的過程如下:

1 系統初始化時,free鍊錶中的所有頁都可以被分配。

2 有資料請求的時候,將從磁碟讀取到的block放入lru鍊錶中,該操作直接將所有的block置為young並插入鍊錶頭部,直到lru長度達到buf_lru_old_min_len。

3 當lru長度達到buf_lru_old_min_len時,innodb會做如下操作:

3.1 將所有的lru塊都置為old(buf_lru_old_init)

3.2 排程buf_lru_old_adjust_len函式,將buf_pool->lru_old調整到合適的位置。

4 之後,每次有新的頁要插入lru時,排程buf_lru_add_block函式,並將old標記為true,將該頁插入到buf_pool->lru_old的next位置

5 若第四步中的資料頁再次被訪問,innodb排程buf_lru_make_block_young函式將該頁放到lru鍊錶頭部。

6 free鍊錶分配完,此時需要從lru尾部尋找可以釋放的block,該操作由buf_lru_search_and_free_block執行。

tips:

這裡需要注意一點,lru鍊錶尾部的block確實可以被釋放,但是要滿足兩個前提:頁不是髒的;頁沒有被其他執行緒使用。因為髒頁總是要重新整理到磁碟的,所以當髒頁要被替換的時候,需要首先將其刷入磁碟中。用於釋放尾部block的函式buf_lru_free_block中有乙個約束:

if (!buf_flush_ready_for_replace(block))

block = ut_list_get_prev(lru, block)
然後繼續判斷該block是否能被釋放。完整的**如下,我自己加了部分注釋:

ibool

buf_lru_search_and_free_block(

/**************************=

*//*

out: true if freed

*/ulint n_iterations)

/*in: how many times this has been called

repeatedly without result: a high value means

that we should search farther; if value is

k < 10, then we only search k/10 * [number

of pages in the buffer pool] from the end

of the lru list

*/ block =ut_list_get_prev(lru, block); //尾部的頁不能被釋放,尋找其前面的block,繼續迴圈

distance++;

if (!freed && n_iterations <= 10

&& distance > 100 + (n_iterations * buf_pool->curr_size)

/ 10

) }

if (buf_pool->lru_flush_ended > 0

)

if (!freed)

mutex_exit(&(buf_pool->mutex));

return

(freed);

}

這兩天都在看innodb的緩衝池原始碼,暫時來說只有這一點收穫。這裡使用的c語言雖然超過了我的認識水平(我基本上只能看懂簡單的c**,有指標勉強能懂),但是加上注釋和參考資料,還是感覺比簡單的看文件要來的痛快的多。

InnoDB原始碼分析 緩衝池(三)

昨天寫到了innodb緩衝池的預讀 innodb原始碼分析 緩衝池 二 最後因為著急看歐洲盃,沒有把線性預讀寫完,今天接著寫。線性預讀是由這個函式實現的 buf read ahead linear,和隨機預讀一樣,首先是要確定區域邊界,這個邊界內被訪問過的page如果達到乙個閾值 buf read ...

innodb的緩衝池(buffer pool)

緩衝區是主記憶體中儲存訪問的表和索引的區域。緩衝池允許經常訪問的資料直接從記憶體處理,更加高效。為了提高大容量讀取操作的效率,緩衝池被劃分為可以容納多行資料的頁 page 為了高效管理快取,緩衝池被實現為頁 page 的列表,快取刪除用lru演算法的變種實現。下圖為緩衝池的結構 head部儲存的是最...

InnoDB原始碼分析 事務日誌(一)

在之前的文章 innodb的wal方式學習 裡,我分析了wal是什麼,觸發時機,最近剛好在看redo log方面的原始碼,就再次聊一聊這方面的事情吧。大家都知道這個引數 innodb flush log at trx commit,該引數用於控制redo buffer中的內容寫入日誌的時機,一般來說...