干貨分享

各位大佬,大家下午好 (敬禮)。下面我給大家分享一些 mysql面試干貨!

本次分享儒猿專欄《從零開始帶你成為MySQL實戰(zhàn)優(yōu)化高手》中InnoDB存儲引擎的執(zhí)行原理

分享思路: 會先從一個簡單的update語句入手,分析它在被執(zhí)行時是如何與InnoDB存儲引擎的各種機制結合起來的,并依次完成整個update語句的執(zhí)行,在本次分享開始前可以先嘗試思考如下面試題:

1.數(shù)據(jù)頁和緩存頁是什么?如何知道哪些緩存頁是空閑的,哪些緩存頁是可被清除的?

2.mysql預讀機制了解過嗎,什么情況下會觸發(fā)它?mysql是為了應對什么樣的場景才設計預讀機制?

3.類比redis在內存中也存在冷熱數(shù)據(jù)共存的場景,如何考慮利用lru鏈表解決預讀機制的思想、來對redis緩存的設計進行優(yōu)化?

4.內存極度不夠用情況下,可能每當要加載一個數(shù)據(jù)頁時就要先把一個緩存頁刷到磁盤中,出現(xiàn)雙倍IO的性能問題,對于這種現(xiàn)象如何考慮優(yōu)化Mysql內核參數(shù)來盡量避免該情況的性能損耗?

下面給大家看下 本次分享的完整流程圖:

image.png

大家看到這個流程圖不要懵逼。我下面來一步一步的解析下這個流程圖。
這個流程圖拆成四部分
1: 磁盤數(shù)據(jù)如何加載到mysql中?
2: 在InnoDB中執(zhí)行更新操作
3: 緩沖池內存不足觸發(fā)臟頁刷盤
4: mysql的預讀機制帶來的問題以及優(yōu)化后的lru鏈表對該問題的解決

一,磁盤數(shù)據(jù)如何加載到mysql中?
一般我們要更新一條數(shù)據(jù),數(shù)據(jù)一開始肯定是存放在磁盤中的,用到時才會被加載到mysql,存放的數(shù)據(jù)在邏輯概念上我們稱為表,物理層面上在磁盤中是按數(shù)據(jù)頁形式存放的,那么加載到mysql中的就稱為緩存頁。

每個緩存頁都有對應的一份描述信息,存放了緩存頁的一些元數(shù)據(jù)相關的一些信息,通過描述信息可以快速定位到緩存頁,最開始描述信息指向的緩存頁當然都是空閑沒有數(shù)據(jù)的,從磁盤加載數(shù)據(jù)頁信息流程如下圖所示:

image.png

那么從磁盤中加載一個數(shù)據(jù)頁到mysql中真的就這么簡單嗎?會不會同一份數(shù)據(jù)頁加載到mysql中出現(xiàn)重復加載的情況?如何快速知道當前數(shù)據(jù)頁是否已經加載到mysql中了?

這時候可能很多人已經想到了:緩存。對于已經加載到mysql中的數(shù)據(jù)頁,我們大可以設計一個緩存將加載過的數(shù)據(jù)頁信息緩存一下,一方面可以防止同一份數(shù)據(jù)頁重復加載到mysql,另一方面當我們需要使用到數(shù)據(jù)頁的信息時,可以通過緩存信息快速定位mysql中對應的緩存頁,沒錯,InnoDB存儲引擎中就是按照這樣的思路設計了一個數(shù)據(jù)頁緩存:

當一條update語句執(zhí)行時,通過sql語句中的數(shù)據(jù)庫名和表名解析可以知道我們需要加載的數(shù)據(jù)頁處于哪個表空間,根據(jù)sql語句本身也可以通過一致性算法得數(shù)據(jù)頁號(具體的sql解析和算法這里暫可簡單了解下),根據(jù)數(shù)據(jù)頁號和表空間號,可以從數(shù)據(jù)頁緩存中(本質也就是一個哈希表)得到對應緩存頁地址,通過緩存頁地址我們直接就可以到InnoDB的緩沖池中定位到緩存頁;當然,如果數(shù)據(jù)頁還沒有加載過,緩存頁地址肯定是不存在,此時就需要從磁盤中加載數(shù)據(jù)頁到mysql中了,如下圖所示:

image.png

這個時候又有一個問題,既然現(xiàn)在我們已經知道磁盤中的數(shù)據(jù)頁是加載到buffer pool緩沖池中的,那么我們怎么樣才能知道哪些緩存頁是空閑的?哪些緩存頁是沒有被加載過數(shù)據(jù)頁信息的呢?

畢竟加載過的數(shù)據(jù)的緩存頁和沒加載過數(shù)據(jù)的緩存頁混在一起,倘若此時想找一個空閑的緩存頁肯定也是一件很麻煩的事。InnoDB存儲引擎在設計時當然也考慮到了這點,這里它引入了free鏈表這個數(shù)據(jù)結構,將那些還沒有被使用的緩存頁的描述信息用雙向循環(huán)鏈表給組合在一起,需要用到時就卸一個節(jié)點出來存放數(shù)據(jù)頁信息,如下圖所示:

image.png

此時數(shù)據(jù)頁被加載到緩存頁了,緩存頁中已經有數(shù)據(jù)了,相關的變動信息肯定也要回寫到描述信息中,并且現(xiàn)在因為緩存頁已經有數(shù)據(jù),就不能再待在free鏈表中了,就需要將該緩存頁對應的描述信息節(jié)點從free鏈表給摘掉,轉移到了lru鏈表中,如下圖所示:

image.png

lru鏈表實現(xiàn)的目的就是為讓哪些被訪問的緩存頁能夠盡量排到靠前位置,那么此時如果此時內存不夠需要淘汰掉一些緩存頁時,此時就可以到lru鏈表尾部,將哪些最近最少被訪問的尾部節(jié)點給刷盤釋放緩存頁騰出內存來。

到這里為此,為了更新一個sql,我們已經把該sql所需要的數(shù)據(jù)、通過InnoDB存儲引擎的各種底層機制,給加載到了Buffer Pool緩沖池中了,接下來就是在InnoDB中執(zhí)行更新操作。

二: 在InnoDB中執(zhí)行更新操作

此時我們需要的數(shù)據(jù)已經從磁盤中加載到緩沖池中了,下一步當然就是執(zhí)行更新操作了:先對需要更新的那行數(shù)據(jù)加鎖、原始數(shù)據(jù)寫一份到redo log中便于可能的回滾操作、執(zhí)行update操作,此時緩存頁的數(shù)據(jù)就被更新了,當然就和磁盤中的數(shù)據(jù)頁的數(shù)據(jù)就不一致了,這樣的緩存頁我們稱之為臟頁,如下圖所示:

image.png

那么,如何才能知道緩沖池中,那些緩存頁是臟頁呢?如果能把臟頁和空閑緩存頁分離出來,我們就可以把那些臟頁的數(shù)據(jù)及時給刷到磁盤中、再釋放掉臟頁內存,在內存不夠的情況下不就可以重復利用了嗎。

這里InnoDB的設計方法類似free鏈表,設計了一個flush鏈表,也就是那些在緩沖池中被更新過數(shù)據(jù)的緩存頁,這些緩存頁的描述信息都會被添加到flush鏈表中(這里提到的free鏈表、lru鏈表、flush鏈表都是雙向循環(huán)鏈表,且節(jié)點都為緩存頁的描述信息,其中flush鏈表的節(jié)點同時也在lru鏈表中),如下圖所示:

image.png

三:緩沖池內存不足觸發(fā)臟頁刷盤

經過以上流程執(zhí)行了一段時間后,直到InnoDB緩沖池中的內存即將不夠用了,此時如果再來一條sql語句的更新操作,要想成功把磁盤中的數(shù)據(jù)加載到緩存頁中,就需要先清理下內存中的緩存頁了。通過之前提到的lru鏈表,可以找到lru鏈表表尾的節(jié)點,這些節(jié)點之所以在表尾,是因為基本上沒什么人訪問它們,那它們在內存不夠用的場景下,當然要被優(yōu)先給清理掉啊;

因為flush鏈表的節(jié)點也在lru鏈表中,此時在緩存頁清理時需要做一個簡單的判斷:若緩存頁既在lru表尾的節(jié)點同時也在flush鏈表中,就需要先把臟頁給刷盤了,然后再釋放掉緩存頁的內存,保證那些事務修改的數(shù)據(jù)能夠落庫;若緩存頁不在flush鏈表,那更簡單直接釋放緩存頁內存,然后將這些釋放完內存緩存頁的描述信息,重現(xiàn)給添加到free鏈表中,完成一次大的循環(huán)(free鏈表->lru鏈表->flush鏈表->free鏈表),如下圖所示:

image.png

四: mysql的預讀機制帶來的問題以及優(yōu)化后的lru鏈表對該問題的解決方案

mysql預讀機制可能會擾亂我們之前設想的lru鏈表的處理邏輯。當一個數(shù)據(jù)頁被加載到緩沖池中時,可能順帶會把其他無關緊要的數(shù)據(jù)頁也加載到緩沖池中,這些順帶加載到內存的數(shù)據(jù)頁,它們往往被訪問的頻率是非常低的,

但是由于lru鏈表的特點,新加入的總是會優(yōu)先被排在lru的鏈表頭,導致這些順帶進來的、訪問頻率比較低的緩存頁排在比較靠前的位置,導致free鏈表不夠時,lru鏈表反而會把那些本來訪問頻率較高、但是此時被排擠到lru鏈表尾的緩存頁給刷盤清理了,這是很不合理的。

優(yōu)化后的lru鏈表主要引入了冷熱數(shù)據(jù)分離的思想解決了mysql預讀機制帶來的問題。把lru鏈表分為熱數(shù)據(jù)區(qū)和冷數(shù)據(jù)區(qū),熱數(shù)據(jù)區(qū)主要存放那些訪問頻率高的緩存頁,冷數(shù)據(jù)區(qū)存放訪問頻率較低的緩存頁;

從磁盤加載數(shù)據(jù)到lru鏈表時,首先會將加載到的緩存頁直接先放到冷數(shù)據(jù)鏈的表頭,如果1000ms(默認,可配置)后冷數(shù)據(jù)的緩存頁又被訪問了,此時就認為這些1000ms之后被訪問的緩存頁,在不久的未來可能還會被訪問,可以認為它們是熱數(shù)據(jù)了,就會把這些緩存頁從冷數(shù)據(jù)區(qū)的鏈表給移動到熱數(shù)據(jù)區(qū)鏈表的表頭,通過該步驟可以將熱數(shù)據(jù)從冷數(shù)據(jù)堆中給巧妙的分離出來,如下圖所示:

image.png

此時如果要加載其他數(shù)據(jù)頁發(fā)現(xiàn)緩沖池內存不夠,實際上后臺一直會有一個線程開啟的一個定時任務,不斷的從lru鏈表的尾部將緩存頁給刷到磁盤中并釋放緩存頁,lru鏈表冷熱數(shù)據(jù)分離的設計,確保了定時任務從lru鏈表尾部回收的緩存頁都是訪問頻率很低的數(shù)據(jù),對性能的影響也就降到了最低。

獻丑了。本次分享到此結束了。最后大家一起思考分享之前的幾個面試題:

1.數(shù)據(jù)頁和緩存頁是什么?如何知道哪些緩存頁是空閑的,哪些緩存頁是可被清除的?

2.mysql預讀機制了解過嗎,什么情況下會觸發(fā)它?mysql是為了應對什么樣的場景才設計預讀機制?

3.類比redis在內存中也存在冷熱數(shù)據(jù)共存的場景,如何考慮利用lru鏈表解決預讀機制的思想、來對redis緩存的設計進行優(yōu)化?

4.內存極度不夠用情況下,可能每當要加載一個數(shù)據(jù)頁時就要先把一個緩存頁刷到磁盤中,出現(xiàn)雙倍IO的性能問題,對于這種現(xiàn)象如何考慮優(yōu)化Mysql內核參數(shù)來盡量避免該情況的性能損耗?

有什么不懂的,可以問我。以上都是 儒猿MYSQL專欄內容!

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容