1. 程式人生 > >2017資料庫大會實錄-MySQL核心引數含義的原始碼解析

2017資料庫大會實錄-MySQL核心引數含義的原始碼解析

5月11-13日在北京國際會議中心舉行資料庫大會,有幸得友人推薦在大會上講了一場。源於自己曾經參加一些技術大會的感受——抱著學習的目的,非常興奮非常飢渴的過去了,但往往也是相當飢渴的回來了,並不是老師分享的內容沒有營養跟價值,而往往是老師講得內容太高大上,太豐富,營養價值過高,難以在短短的一個小時內吸收消化,所以依然是飢餓的 。基於這樣的感受,所以作者在這次大會分享一個“接地氣”的內容,心想從事mysql運維或者使用mysql的朋友,或多或少的都想知道資料庫內部的執行機制,以及更想知道資料庫引數該如何設定,才能使資料庫的效能得到最大的發揮,所以以”MySQL核心引數含義的原始碼解析”為題目,來進行分享,希望讓聽講的朋友們對mysql引數有更深入的理解。

目地是美好的,但效果卻很骨感的。為了抓緊這短暫40~50分鐘的時間,中間毫不間斷的,差不多以作者最快的語速,側著身拿著熒光筆,不停在這大螢幕上一邊筆畫,一邊講解,目的是希望在有限的時間內把所有必要的細節都講到,以至忘了這是一場演講,而不是真正地當老師在講課,杯具。。。。。。

當我以最快的速度在規定的時間把內容講完,臺下的所認識的朋友跟我反饋說,前面一半跟上了,後面稍微沒留神,沒有跟上,後面就完全聽不懂了。太失落了,跟我想的效果完全不一樣啊,我講得夠詳細了,只不過語速快了點。但令作者還有些安慰的是:有陌生的聽眾在分享後當場跑過來跟我反饋說,你分享的內容真的很好,連程式碼的細節都講到了,非常受益。兄弟,跪謝了,總算還有人懂我。

既然是分享,目的是讓更多的希望收穫知識的朋友得到分享的內容。作者現將ppt的內容,以及解析的內容以文子的形式描述出來,希望讓沒參加大會的朋友,也能比較容易的瞭解這次分享的內容。同時,也能夠讓在大會上沒有聽明白的朋友繼續補習。堅守到最後一場,聽作者演講,沒有半點收穫,作者豈不是愧對你們了。 再次謝謝堅持下來聽我分享的朋友。

純乾貨分享內容拿走不謝,但不歡迎網站或者公眾號未經同意轉載!!!

下面是分享後整理的實錄內容:

mysql

大家下午好,今天我分享的主題是”mysql核心引數含義的原始碼解析“。mysql的引數非常多,鑑於今天下午時間的關係,我只會講其中一部分引數,這部分引數是關於buffer pool . 我們通過解析buffer pool(快取池)的原始碼,來直觀地瞭解這些引數的真正含義。

這個是今天要講的內容:我們首先簡單介紹buffer pool的工作機制,然後去解析buffer pool的核心函式。(講完後,因為看到上面的目錄,有聽眾問我是不是寫了這本書,要問我買。其實我只是借用了word生成目錄的功能。)

在正式講今天的內容之前,我們來簡單聊一下,在mysql日常運維過程中,我們所經歷的一些困惑。例如有時候資料庫莫名其妙的變慢了,通常(或者可能)在10分鐘前還是好好的,現在卻出現了問題。在我們進行診斷的時候,可能會發現cpu,或者io , memory等出現了一些狀況。甚至有個時候,將這些指標跟10分鐘之前比較,看不出任何異常。

假如有幸我們發現了一些異常現象,但這些異常的現象,是產生問題本身的原因還是問題出現後表現出來的現象?僅僅從現象本身來看,是不太好定位問題產生的原因的。

在遇到一些不好定位的問題之後,跟同事討論後,但又各自可能有不同的意見,不同的觀點,這通常是我們最大的困惑——問題沒有解決,而且對問題背後的原因沒有定論,接下來不知道該如何處理?但計算機世界是一個客觀的世界,不存在主觀性,問題背後的原因一定是確定的,雖然有可能是多種因數在特定條件下綜合在一起的結果。這類問題往往是最難定位的,從片面地維度來尋找答案都是失真的,錯誤的。所以,在這個時候,當我們對資料庫內部越來越瞭解,瞭解得越來越全面,定位這類問題就會越來越準確,越來越接近問題的本質。下面就讓我們來一起對mysql的知識進行深入的瞭解。

資料庫

下面我們正式來講今天的內容: mysql的引數非常多,我簡單地列出一部分並做一下簡單的分類。

跟事務安全/事務提交相關的引數,例如最著名innodb_flush_log_at_trx_commit, sync_binlog 。

跟各種型別cache相關的引數,例如thread_cache_size,

table_open_cache。

其他重要的引數,例如跟併發控制相關的引數,例如max_connection, innodb_thread_concurrency.  我們今天要講這部分引數,就是buffer pool(快取池)相關的, 這裡提出了多個關於buffer pool的引數,不知大家對這些引數的含義是否有比較清晰的、深入的理解? 如果絕大部分朋友對這些引數尚未完全瞭解,那很好。 我們今天下午的分享就比較有價值 。後面會對buffer pool原始碼進行解析,會比較清楚地介紹到這些引數在哪些函式中被使用到,通過了解這些函式的功能跟實現,也就能直觀地瞭解這些引數的含義。

buffer pool

我們先來簡單地看一下buffer pool的工作機制。根據我的理解, buffer pool兩個最主要的功能:一個是加速讀,一個是加速寫。加速讀呢? 就是當需要訪問一個數據頁面的時候,如果這個頁面已經在快取池中,那麼就不再需要訪問磁碟,直接從緩衝池中就能獲取這個頁面的內容。加速寫呢?就是當需要修改一個頁面的時候,先將這個頁面在緩衝池中進行修改,記下相關的重做日誌,這個頁面的修改就算已經完成了。至於這個被修改的頁面什麼時候真正重新整理到磁碟,這個是buffer pool後臺重新整理執行緒來完成的,後面會詳細講到。

在實現上面兩個功能的同時,需要考慮客觀條件的限制,因為機器的記憶體大小是有限的,所以mysql的innodb buffer  pool的大小同樣是有限的。在通常的情況下,當資料庫的資料量比較大的時候,快取池並不能快取所有的資料頁,所以也就可能會出現,當需要訪問的某個頁面時,該頁面卻不在快取池中的情況,這個時候就需要從磁碟中將這個頁面讀出來,載入到快取池中,然後再去訪問。這樣就涉及到隨機的物理io,也就延長了訪問頁面所消耗的時間。

這樣的情況是一個bad case,是我們期望儘量避免的——因此需要想辦法來提高快取的命中率。 innodb buffer pool採用經典的LRU列表演算法來進行頁面淘汰,以提高快取命中率。將快取的頁面按照最近使用的次數跟時間進行排序,佇列最末尾的頁面將會最先被淘汰。這個機制在後面會結合原始碼詳細講解。同時,在LRU列表的中間位置打了一個old標識,可以簡單的理解為將LRU列表分為兩個部分,這個標記到LRU列表頭部的頁面稱之為yong的頁面,這個標誌到LRU列表尾部的頁面稱之為old頁面。再進行抽象的話,我們簡單地理解為快取池被分成兩個池子,一個叫young池子,一個叫old池子。當一個頁面從磁碟上載入快取池中的時候,會將它排放在這個old標識之後的第一個位置,也就是說放在了old池子中。這個機制的作用就是,在做大表的一次性全表掃描的時候,大量新進來的頁面,是存放在old池子中的,當old池子的大小不夠快取新進來的頁面的時候,也只是在old池子中內部進行迴圈沖洗,這樣就不會沖洗young池子中的熱點頁面,從而保護了熱點頁面。這就是LRU列表的機制。

另外,前面我們講到頁面更新是在快取池中先進行的,所以需要考慮這些被修改的頁面什麼時候重新整理到磁碟?以什麼樣的順序重新整理到磁碟?在innodb buffer pool中,採用的方式是將頁面在快取中的按照第一次修改時間,也就是變成髒頁的時間進行排序,flush列表進行排序,由後臺重新整理執行緒依次重新整理到磁碟,實現修改落地到磁碟。

buffer pool

我們簡單介紹了buffer pool的工作機制,我們現在來看buffer pool 裡面最重要的三個列表,前面已經講了兩個列表,LRU列表以及flush列表,也就是髒頁重新整理列表。現在再補充一個列表——空閒列表。空閒列表中的記憶體塊,是沒有存放任何資料頁的記憶體塊。當沒有在快取池中的頁面需要被訪問時,它需要先被載入到快取池中,從而需要從空閒列表中取出一個空閒記憶體塊來快取這個頁面。

在這裡提一下,一個bufferpool 可能會分成好幾個buffer pool instance , 在mysql5.7中,如果不顯示設定innodb_buffer_pool_instances這個引數,當innodb buffer size 大於1G的時候,就會預設會分成8個instances,如果小於1G,就只有1個instance。

資料頁

下面我們來看一下一個資料頁的訪問流程。

1.  當訪問的頁面在快取池中命中,則直接從緩衝池中訪問該頁面。

2.  如果沒有命中,則需要將這個頁面從磁碟上載入到快取池中,因此需要在快取池中的空閒列表中找一個空閒的記憶體塊來快取這個從磁碟讀入的頁面。

3.  但存在空閒記憶體塊被使用完的情況,不保證一定有空閒的記憶體塊。假如空閒列表為空,沒有空閒的記憶體塊,則需要想辦法去產生空閒的記憶體塊。

4.  首先去LRU列表中找可以替換的記憶體頁面,查詢方向是從列表的尾部開始找,如果找到可以替換的頁面,將其從LRU列表中摘除,加入空閒列表,然後再去空閒列表中找空閒的記憶體塊。這就是LRU列表中的頁面淘汰機制。

5.  如果在LRU列表中沒有找到可以替換的頁,則在列表最末尾選擇一個頁面進行重新整理,重新整理後加入空閒列表,然後再去空閒列表中取空閒記憶體塊。

因為空閒列表是一個公共的列表,所有的使用者執行緒都可以使用,存在爭用的情況。因此,自己產生的空閒記憶體塊有可能會剛好被其他執行緒所使用,所以使用者執行緒可能會重複執行上面的查詢流程,直到找到空閒的記憶體塊為止。

函式

畫圖跟表述可能沒有完全清楚地表達,下面我們來看一下查詢空閒記憶體塊的原始碼:

這個函式的名稱是buf_LRU_get_free_block,單純從函式的命名來看,我們就能大概猜出這個函式的作用——-獲取空閒的記憶體塊。我們來解析這個函式:

1.  首先看函式的開頭部分——我們直接看程式碼註釋,ifthere is a block in the free list, take it .從空閒列表中去獲取block, 如果獲取到,就返回。 這個return, 是該函式的唯一返回出口,也就是一定要找到空閒的block才返回,否則一直迴圈找下去。

2.  如果沒有找到,則從LRU列表的尾部開始找可以替換的BLOCK,第一次查詢最多隻掃描100個頁面,迴圈進行到第二次時,會查詢深度就是整個LRU列表。如果找到可以替換的頁,則將其加入到空閒列表,然後再去空閒列表中找。

3.  如果在LRU列表中沒有找到可以替換的頁,則進行單頁重新整理, 將髒頁重新整理到磁碟之後,然後將釋放的記憶體塊加入到空閒列表。然後再去空閒列表中取。為什麼只做單頁重新整理呢?因為這個函式的目的是獲取空閒記憶體頁,進行髒頁重新整理是不得已而為之,所以只會進行一個頁面的重新整理,目的是為了儘快的獲取空閒記憶體塊。

中間還有一些細節,包括設定重新整理事件,以請求後臺重新整理執行緒進行髒頁重新整理,以及當進行第三次迴圈時,執行緒自己先sleep 10 毫秒,然後再去做頁面重新整理。這些將不再祥描。

通過了解了空閒頁面的查詢流程之後,我們知道,如果需要重新整理髒頁來產生空閒頁面或者需要掃描整個LRU列表來產生空閒頁面的時候,查詢空閒記憶體塊的時間就會延長,這個是一個base case,是我們希望儘量避免的。因此,innodb buffer pool 中存在大量可以替換的頁面,或者free 列表中一直存在著空閒記憶體塊,對快速獲取到空閒記憶體塊起決定性的作用。 在innodbbuffer pool的機制中,是採用何種方式來產生的空閒記憶體塊,以及可以替換的記憶體頁的呢?這就是我們下面要講的內容——通過後臺重新整理機制來產生空閒的記憶體塊以及可以替換的頁面。

引數

在講innodb buffer pool的重新整理機制之前,我們再來簡單看一下有關buffer pool 的引數,這些引數將在後面的原始碼解析中使用到。

這就是我們接下來要講的內容:這些函式是跟快取池後臺頁面重新整理相關的函式。後臺重新整理的動作由後臺重新整理協調執行緒觸發,該執行緒的所有工作內容均由

buf_flush_page_cleaner_coordinator函式完成,我們後面簡稱它為協調函式。

其會呼叫page_cleaner_flush_pages_recommendation函式,我們後面簡稱它為建議函式或者推薦函式。在執行重新整理之前,會用建議函式生成每個buffer pool需要重新整理多少個髒頁的建議。具體是怎麼生成建議的呢?就是子目錄的內容,後面會詳細講到。生成完重新整理建議之後,其後就會產生請求重新整理的事件,後臺重新整理執行緒在收到請求重新整理的事件後,會執行pc_flush_slot函式對某個快取池進行重新整理,重新整理的過程首先是對lru列表進行重新整理,執行的函式為buf_flush_LRU_list,完成LRU列表的重新整理之後,就會根據建議函式生成的建議對髒頁列表進行重新整理,執行的函式為buf_flush_do_batch。

所有的buffer pool 都已經開始重新整理之後,就開始等待所有buffer pool重新整理的完成,等待函式為pc_wait_finished.

上面介紹了協調函式的工作流程,下面我們就逐步來解析這個函式以及相關子函式。

函式

這個協調函式的作用前面已經講過,是進行重新整理迴圈的排程的。稍微補充一下,它期望每秒鐘對buffer pool 進行一次重新整理排程。如果相鄰兩次重新整理排程的間隔超過4000ms ,也就是4秒鐘,mysql的錯誤日誌中會記錄相關資訊,意思就是“本來預計1000ms的迴圈花費了超過4000ms的時間。

原始碼

我們來看一下後臺重新整理協調函式的原始碼:

左邊是迴圈超時時,在錯誤日誌記下相關資訊。右邊是呼叫對每個buffer pool 生成需要重新整理多少髒頁的建議函式。

主體流程

接下來,我們來看後臺重新整理協調函式的主體流程。

1.  呼叫建議函式,對每個緩衝池例項生成髒頁重新整理數量的建議。

2.  生成重新整理建議之後,通過設定事件的方式,向重新整理執行緒發出重新整理請求.

3.  後臺重新整理的協調執行緒會作為重新整理排程總負責人的角色,它會確保每個buffer pool 都已經開始執行重新整理。如果哪個buffer pool的重新整理請求還沒有被處理,則由重新整理協調執行緒親自重新整理,且直到所有的buffer pool instance都已開始/進行了重新整理,才退出這個while迴圈。

4.  當所有的buffer pool instance的重新整理請求都已經開始處理之後,協調函式(或協調執行緒)就等待所有buffer pool instance的重新整理的完成。如果這次重新整理的總耗時超過4000ms,下次迴圈之前,會在資料庫的錯誤日誌記錄相關的超時資訊。

前面我們反覆講到,每個buffer pool 需要重新整理多少頁面是由建議函式生成的,它在做重新整理建議的時候,具體考慮了哪些因素?現在我們來詳細解析。

在講這段內容之前,我們先來了解兩個引數:

innodb_io_capacity與innodb_io_capacity_max,這兩個引數大部分朋友都不陌生,設定這個引數的目的,是告訴mysql資料庫,它所在伺服器的磁碟的隨機IO能力。mysql資料庫目前還沒有去自己評估伺服器磁碟IO能力的功能,所以磁碟io能力大小由這個引數提供,以便讓資料庫知道磁碟的實際IO能力。這個引數將直接影響建議重新整理的頁面的數量。

我們來簡單看一下推薦函式中的內容:

首先它會計算當前的髒頁重新整理平均速度以及重做日誌的生成平均速度。但這個函式並不是每次被呼叫時,都計算一次平均速度。它是多久計算一次的呢?這個是由資料庫引數

innodb_flushing_avg_loops 來決定的。 預設是30,當這個函式被呼叫了30次之後或者經過30秒之後,重新計算一次平均值。我們暫且簡單理解為30秒鐘。計算規則是當前的平均速度加上最近30秒鐘期間的平均速度再除以2得出新的平均速度。兩個平均值相加再平均,得出新的平均值。這樣的平均值能明顯的體現出最近30秒的速度的變化。

接下來,它會根據innodbbuffer pool的髒頁百分比來計算innodb_io_capacity 的百分比. 然後會根據重做日誌中的活躍日誌量的大小,也就是lsn的age,佔重做日誌檔案大小的百分比來計算innodb_io_capacity的百分比 . 將這兩項計算結果進行比較,取大的值作為最終的innodb_io_capacity 的百分比,用變數pct_total 為儲存。假如計算出來的得到pctl_total為90,而資料庫引數innodb_io_capacity設定為1000,則根據這兩個因素再結合所設定的磁碟io能力,得出的建議就為重新整理900個髒頁。

然後,會根據前面計算重做日誌的生成平均速度,來計算建議每個buffer pool instance 重新整理多少髒頁以及所有pool buffer的重新整理總量。之所有會基於這個因素來考慮,我認為是這樣的:新產生的重做日誌是活躍的重做日誌,根據活躍日誌的生成速度來計算需要重新整理的髒頁的數量,從而將使活躍日誌的過期速度跟生成速度達到一個均衡,這樣控制了活躍的重做日誌在一個正常的範圍,保障了重做日誌檔案一直有可以使用的空間。在這裡簡單說明一下活躍的重做日誌跟不活躍的重做日誌的區別:活躍日誌是指其記錄的被修改的髒頁還沒有被重新整理到磁碟,當mysql 例項crash之後,需要使用這些日誌來做例項恢復。

再接下來,通過上面的計算,我們從不同維度分別得出三個建議重新整理的數量:分別為當前的髒頁重新整理的平均速度,也就是一秒鐘重新整理了多少髒頁;根據髒頁百分比,以及活躍日誌量的大小,以及所設定的innodb_io_capacity 引數所得出建議重新整理的數量;以及根據重做日誌產生速度計算得出的建議重新整理數量。將這三個值相加之後再平均,得出的就是考慮了上面所有因素的一個綜合建議,由變數n_pages儲存。

接下來,這個建議重新整理的總量n_pages會跟innodb_io_capacity_max這個引數進行比較,也就是建議重新整理的總量最大不能超過所設定的磁碟最大隨機io能力。

最後,生成最終的重新整理建議。生成最終的重新整理建議時,會考慮當前資料庫的活躍日誌量的大小,當前活躍日誌比較少的時候,認為重做日誌檔案有足夠可以使用的空間(以變數pct_for_lsn小於30為依據),則不需要考慮每個buffer pool 之間的髒頁年齡分佈不均的情況,每個buffer pool 重新整理相同的數量,數量就重新整理總量除以buffer pool的個數。如果活躍日誌比較多(以變數pct_for_lsn大於等於30為依據),則需要考慮髒頁的年齡在每個buffer pool的分佈不同,每個buffer重新整理不同的數量的髒頁,老的髒頁比較多的buffer pool instance重新整理的數量也就多。

以上就是建議函式生成重新整理建議時的計算流程,下面根據原始碼來分析如何具體考慮這些因素,以便讓我們有非常直觀的理解。

首先來計算平均值,前面已經有比較清楚的講過,現在大家來簡單地看一下這部分程式碼,主要請關注這個if條件:當迴圈次數達到innodb_flush_avg_loops時或者經歷的時間達到該值時,才進行新的平均值的計算。因此,大家清楚了這個引數的含義,是用來指明隔多久計算一次平均值。平均值計算規則就是新平均速度=當前的平均速度+最近這段期間平均速度,再除以2 。

io_capacity

接下來這一段程式碼呢,是首先計算lsn的age, 也就是活躍日誌量的大小,然後呼叫相關函式根據髒頁百分比來計算io_capacity的百分比,用變數pct_for_dirty儲存,然後根據活躍日誌量的大小來計算io_capacity的百分比,用變數pct_for_lsn來儲存,這個值後面會被是使用到,用來決定每個buffer pool是建議重新整理相同的數量的髒頁,還是重新整理不同的數量。當pct_for_lsn<30的時候,建議每個buffer重新整理相同數量的頁面。否則,建議重新整理不同數量的頁面。

最後比較這兩個變數的大小,大的值作為最終的io_capacity的百分比,用變數pct_total儲存。接下來我們將來看看是如何具體跟據這兩項來計算io_capacity的百分比的。

函式af_get_pct_for_dirty()的計算邏輯是:

首先獲取快取池的髒頁百分比,然後根據這個值進行判斷。

如果引數最大髒頁百分比的低水位設定為0(預設值),當dirty_pct大於引數innodb_max_dirty_pages_pct,則返回100, 否則返回0 。

如果設定了最大髒頁百分比的低水位,當髒頁百分比超過該值時,則返回相應的比例。當髒頁百分比越接近最大髒頁百分比,返回比例越接近100。  否則為0。

再來看看根據lsn的age,即活躍日誌量來計算io_capacity百分比的規則。

如果活躍日誌量佔日誌檔案大小的百分比小於引數innodb_adaptive_flushing_lwm,即自適應重新整理的低水位,預設是10,則直接返回0。

如果沒有設定自適應重新整理引數innodb_adaptive_flushing_lwm,預設為on ,則需要等待活躍的日誌量大於max_async_age的值,才會返回相應的百分比,否則返回0。可以簡單的理解為,如果沒有開啟自適應重新整理,則必須等待活躍日誌量的過大,大到存在危害資料庫的可用性風險時,才開始考慮基於活躍日誌量的大小來進行髒頁重新整理。

如果開啟了自適應重新整理,活躍日誌量所佔百分比大於自適應重新整理的低水位時(innodb_adaptive_flushing_lwm),返回相應的百分比。具體計算公式檢視ppt上的內容。

接下來,我們來看看是怎麼根據重做日誌的生成速度來計算每個buffer需要重新整理多少髒頁的。這一段程式碼,不涉及資料庫的任何引數,程式碼的功能就是根據重做日誌生產的速度,來計算每個buffer需要重新整理多少頁面以及所有buffer pool所建議重新整理的總量,但這個不是最終的建議。

首先,根據前面計算得出的lsn_avg_rate,即重做日誌產生的平均速度,計算出一個target_lsn號。

然後從每一個buffer pool的髒頁列表的隊尾開始取出髒頁,將髒頁的old_modifiaction(最小的lsn)跟target_lsn進行比較,這裡簡單的說明一下髒頁的oldest_modification的含義,它表示的是髒頁第一次修改時的lsn號,也就是髒頁的最小lsn號。如果它小於target_lsn, 然後將其作為重新整理物件進行計數,否則,退出這個buffer pool 內的迴圈.因為重新整理列表時按照髒頁的最小lsn號進行排序的,前面的髒頁的最小lsn都大於target_lsn ,所以不需要再繼續找下去。

從上面的計算方式可以看出,當重做日誌生成的平均速度越大,target_lsn 就越大,同時,如果buffer_pool中的髒頁的old_modition小於target_lsn的數量越多,也就是老的髒頁越多,被建議重新整理的頁面就越多。

buffer

這張ppt上一張ppt程式碼段的註釋。

生成最終的重新整理建議。

通過前面的計算,我們從不同維度分別得出三個建議重新整理的數量,然後將這個三個值進行平均,得出了綜合所有因素的一個重新整理建議總量,由變數n_pages儲存。

影響重新整理總量的因素有:髒頁的百分比,活躍日誌量的大小,當前redo生成的平均速度,當前髒頁重新整理平均速度,以及髒頁的age分佈情況,以及引數innodb_io_capacity,innodb_io_capacity_max。

前面根據活躍日誌量計算所得出的io_capacity的百分比的這個變數——pct_for_lsn,在這裡再次被用到。當pct_for_lsn <30時,認為重做日誌檔案有足夠的可用空間,不需要考慮髒頁的年齡在buffer pool instance之間分佈不均的情況,建議每個buffer重新整理相同的數量,否則,需要考慮髒頁的年齡分佈情況,每個buffer pool instance 所建議重新整理的髒頁數量不同,老的髒頁比較多的buffer pool會被建議重新整理更多地數量。

上面就是完整的重新整理建議函式的解析,裡面涉及到一些相關引數的使用,不知道大家對涉及到引數是否已經瞭解。

pc_request

當生成重新整理建議之後,就設定重新整理請求事件,請求重新整理執行緒進行髒頁批量重新整理。 函式pc_request特別簡單。

1.  將所有bufferpool instances 的重新整理狀態設定為PAGE_CLEANER_STATE_REQUESTED,即申請重新整理.

2.  通過設定事件,喚醒/觸發page cleaner 執行緒呼叫pc_flush_slot函式來進行buffer pool的批量重新整理。

buf_flush_LRU_list

Page_cleaner執行緒收到重新整理請求之後,進行批量重新整理。函式為pc_flush_slot.

  1. 尋找一個狀態為申請重新整理的快取池例項,然後選為重新整理物件,將狀態修改為flushing.。然後執行後面的重新整理。
  2. 執行buf_flush_LRU_list函式進行LRU列表的重新整理,

3.  執行buf_flush_do_batch批量重新整理髒頁列表,該buffer pool instance建議重新整理的數量slot->n_pages_requested作為該函式引數值,也就是依據建議重新整理的頁面數來進行重新整理。

於LRU列表的重新整理的函式buf_flush_LRU_list將scan_depth 變數傳遞最終傳遞給buf_flush_LRU_list_batch 函式, 在通常情況下,可以簡單的理解scan_depth的值來自於資料庫引數innodb_lru_scan_deptch引數。 接下來看buf_flush_LRU_list_batch函式。

我們來看這個函式的迴圈體,我們來看退出迴圈的條件,滿足任何一個條件退出:

1.如果free列表的長度大於innodb_lru_scan_depth,則中止迴圈。
2.被替換(evict_count)+被重新整理(count)的頁面數最多為scan_depth,scan_deptch可以簡單理解為等於innodb_lru_scan_depth.      在看看迴圈裡面的內容:

如果是一個可替換的頁,則執行函式buf_LRU_free_page,將從LRU列表中摘除,其加入free列表。evict_count++

如果是髒頁,則呼叫函式buf_flush_page_and_try_neighbors進行重新整理,重新整理數量累計到count值。

由此我們可以看出innodb_lru_scan_depth引數,在此起非常關鍵的作用,實際上也直接影響了buffer bool instance中的free列表的長度。

對於髒頁列表批量重新整理的函式。

slot->n_pages_requested:為之前介紹的重新整理建議函式

page_cleaner_flush_pages_recommendation為該buffer pool instance所建議的需要重新整理的頁面的數量,實際重新整理的頁面並不一定等於該值。後面將詳細介紹。這個值最終傳遞給buf_do_flush_list_batch函式的min_n引數。

我們來看一下這個函式,該函式主要邏輯也是一個for迴圈,我們來看一下循序中止的條件:

直到 count  >= min_n 或者髒頁列表為空。  即所重新整理的page等於所建議的重新整理數量,或者“髒”頁列表為空。

因為page cleaner執行緒呼叫該函式做批量重新整理的時候,lsn_limit 引數值為極大值,因此無需考慮page的oldest_modification。

重新整理協調函式的執行一個重新整理迴圈的最後一步,等待所有buffer pool instance重新整理的完成。

函式特別簡單,就是設定事件等待,等待所有buffer pool instance重新整理完成的事件觸發。

重新整理完成之後,然後開始下一輪迴圈,如果重新整理在1秒之內完成,則重新整理協調執行緒會有短暫的sleep才會發起下一次重新整理。期望是1秒鐘進行一次所有buffer pool instance的批量重新整理。

當作者把實錄寫完,也跪了——原來有這麼多內容,終於理解現場90%以上的聽眾沒有聽懂的原因了。

實錄太長,有失誤之處煩請留言指出,謝謝!

文章來自微信公眾號:資料庫隨筆