《2017數據庫大會實錄-MySQL核心參數含義的源碼解析》要點:
本文介紹了2017數據庫大會實錄-MySQL核心參數含義的源碼解析,希望對您有用。如果有疑問,可以聯系我們。
5月11-13日在北京國際會議中心舉行數據庫大會,有幸得友人推薦在大會上講了一場.源于自己曾經參加一些技術大會的感受——抱著學習的目的,非常興奮非常饑渴的過去了,但往往也是相當饑渴的回來了,并不是老師分享的內容沒有營養跟價值,而往往是老師講得內容太高大上,太豐富,營養價值過高,難以在短短的一個小時內吸收消化,所以依然是饑餓的 .基于這樣的感受,所以作者在這次大會分享一個“接地氣”的內容,心想從事mysql運維或者使用mysql的朋友,或多或少的都想知道數據庫內部的運行機制,以及更想知道數據庫參數該如何設置,才能使數據庫的性能得到最大的發揮,所以以”MySQL核心參數含義的源碼解析”為題目,來進行分享,希望讓聽講的朋友們對mysql參數有更深入的理解.
目地是美好的,但效果卻很骨感的.為了抓緊這短暫40~50分鐘的時間,中間毫不間斷的,差不多以作者最快的語速,側著身拿著熒光筆,不停在這大屏幕上一邊筆畫,一邊講解,目的是希望在有限的時間內把所有必要的細節都講到,以至忘了這是一場演講,而不是真正地當老師在講課,杯具......
當我以最快的速度在規定的時間把內容講完,臺下的所認識的朋友跟我反饋說,前面一半跟上了,后面稍微沒留神,沒有跟上,后面就完全聽不懂了.太失落了,跟我想的效果完全不一樣啊,我講得夠詳細了,只不過語速快了點.但令作者還有些安慰的是:有陌生的聽眾在分享后當場跑過來跟我反饋說,你分享的內容真的很好,連代碼的細節都講到了,非常受益.兄弟,跪謝了,總算還有人懂我.
既然是分享,目的是讓更多的希望收獲知識的朋友得到分享的內容.作者現將ppt的內容,以及解析的內容以文子的形式描述出來,希望讓沒參加大會的朋友,也能比較容易的了解這次分享的內容.同時,也能夠讓在大會上沒有聽明白的朋友繼續補習.堅守到最后一場,聽作者演講,沒有半點收獲,作者豈不是愧對你們了. 再次謝謝堅持下來聽我分享的朋友.
純干貨分享內容拿走不謝,但不歡迎網站或者公眾號未經同意轉載!!!
下面是分享后整理的實錄內容:
大家下午好,今天我分享的主題是”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后臺刷新線程來完成的,后面會詳細講到.
在實現上面兩個功能的同時,需要考慮客觀條件的限制,因為機器的內存大小是有限的,所以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 里面最重要的三個列表,前面已經講了兩個列表,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 .
接下來這一段代碼呢,是首先計算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的數量越多,也就是老的臟頁越多,被建議刷新的頁面就越多.
這張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特別簡單.
1.? 將所有bufferpool instances 的刷新狀態設置為PAGE_CLEANER_STATE_REQUESTED,即申請刷新.
2.? 通過設置事件,喚醒/觸發page cleaner 線程調用pc_flush_slot函數來進行buffer pool的批量刷新.
Page_cleaner線程收到刷新請求之后,進行批量刷新.函數為pc_flush_slot.
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%以上的聽眾沒有聽懂的原因了.
實錄太長,有失誤之處煩請留言指出,謝謝!
文章來自微信公眾號:數據庫隨筆
轉載請注明本頁網址:
http://www.snjht.com/jiaocheng/4126.html