天天看點

mysql高頻面試題,一文詳解Innodb存儲引擎原理

作者:頓頓有菜ashin

大綱

本文主要講解InnoDB存儲引擎的執⾏原理,在此引入幾個問題:

  1. 資料頁和緩存頁是什麼?如何知道哪些緩存頁是空閑的,哪些緩存頁是可被清除的?
  2. mysql預讀機制了解過嗎,什麼情況下會觸發它?mysql是為了應對什麼樣的場景才設計預讀機制?
  3. 類⽐redis在記憶體中也存在冷熱資料共存的場景,如何考慮利⽤lru連結清單解決預讀機制的思想、來對redis 緩存的設計進⾏優化?
  4. 下面的圖描述了整個執行過程:
mysql高頻面試題,一文詳解Innodb存儲引擎原理

磁盤資料如何加載到mysql中?

⼀般我們要更新⼀條資料,資料⼀開始肯定是存放在磁盤中的,用到時才會被加載到 mysql,存放的資料在邏輯概念上我們稱為表,實體層⾯上在磁盤中是按資料頁形式存放的, 那麼加載到mysql中的就稱為緩存頁。 每個緩存頁都有對應的⼀份描述資訊,存放了緩存⻚的⼀些中繼資料相關的⼀些資訊,通過 描述資訊可以快速定位到緩存頁,最開始描述資訊指向的緩存⻚當然都是空閑沒有資料的,從 磁盤加載資料頁資訊。

mysql高頻面試題,一文詳解Innodb存儲引擎原理

mysql在磁盤以一頁16K的資料按頁存儲,是以在初始化buffer pool的時候,同時也将記憶體按頁劃分,作為緩存頁。

Buffer Pool 是在 MySQL 啟動的時候,向作業系統申請的一片連續的記憶體空間,預設配置下 Buffer Pool 隻有 128MB 。

可以通過調整 innodb_buffer_pool_size 參數來設定 Buffer Pool 的大小,一般建議設定成可用實體記憶體的 60%~80%

當有了Buffer Pool後,它的作用:

  • 當讀取資料時,如果資料存在于 Buffer Pool 中,用戶端就會直接讀取 Buffer Pool 中的資料,否則再去磁盤中讀取。
  • 當修改資料時,首先是修改 Buffer Pool 中資料所在的頁,然後将其頁設定為髒頁,最後由背景線程将髒頁寫入到磁盤

如何快速找到空閑緩存頁

既然現在我們已經知道磁盤中的資料頁是加載到buffer pool緩沖池中的,那麼我們怎麼樣才能知道哪些緩存頁是空閑的?哪些緩存頁是沒有被加載過資料頁資訊的呢?

為了能夠快速找到空閑的緩存頁,可以使用連結清單結構,将空閑緩存頁的「描述資訊」作為連結清單的節點,這個連結清單稱為 Free 連結清單(空閑連結清單)。

mysql高頻面試題,一文詳解Innodb存儲引擎原理

有了 Free 連結清單後,每當需要從磁盤中加載一個頁到 Buffer Pool 中時,就從 Free連結清單中取一個空閑的緩存頁,并且把該緩存頁對應的控制塊的資訊填上,然後把該緩存頁對應的控制塊從 Free 連結清單中移除。

此時資料頁被加載到緩存頁了,緩存頁中已經有資料了,相關的變動資訊肯定也要回寫到 描述資訊中,并且現在因為緩存頁已經有資料,就不能再待在free連結清單中了,就需要将該緩存頁對應的描述資訊節點從free連結清單給摘掉,轉移到了lru連結清單中,如下圖所示

mysql高頻面試題,一文詳解Innodb存儲引擎原理

lru連結清單實作的目的就是為讓哪些被通路的緩存頁能夠盡量排到靠前位置,那麼此時如果此 時記憶體不夠需要淘汰掉⼀些緩存頁時,此時就可以到lru連結清單尾部,将哪些最近最少被通路的 尾部節點給刷盤釋放緩存頁騰出記憶體來。

簡單的 LRU 算法的實作思路是這樣的:

  • 當通路的頁在 Buffer Pool 裡,就直接把該頁對應的 LRU 連結清單節點移動到連結清單的頭部。
  • 當通路的頁不在 Buffer Pool 裡,除了要把頁放入到 LRU 連結清單的頭部,還要淘汰 LRU 連結清單末尾的節點。

簡單的 LRU 算法并沒有被 MySQL 使用,因為簡單的 LRU 算法無法避免下面這兩個問題:

  • 預讀失效;
  • Buffer Pool 污染;

預讀失效

先來說說 MySQL 的預讀機制。程式是有空間局部性的,靠近目前被通路資料的資料,在未來很大機率會被通路到。

是以,MySQL 在加載資料頁時,會提前把它相鄰的資料頁一并加載進來,目的是為了減少磁盤 IO。

但是可能這些被提前加載進來的資料頁,并沒有被通路,相當于這個預讀是白做了,這個就是預讀失效。

如果使用簡單的 LRU 算法,就會把預讀頁放到 LRU 連結清單頭部,而當 Buffer Pool空間不夠的時候,還需要把末尾的頁淘汰掉。

如果這些預讀頁如果一直不會被通路到,就會出現一個很奇怪的問題,不會被通路的預讀頁卻占用了 LRU 連結清單前排的位置,而末尾淘汰的頁,可能是頻繁通路的頁,這樣就大大降低了緩存命中率。

要避免預讀失效帶來影響,最好就是讓預讀的頁停留在 Buffer Pool 裡的時間要盡可能的短,讓真正被通路的頁才移動到 LRU 連結清單的頭部,進而保證真正被讀取的熱資料留在 Buffer Pool 裡的時間盡可能長。

那到底怎麼才能避免呢

優化後的lru連結清單主要引⼊了冷熱資料分離的思想解決了mysql預讀機制帶來的問題。把 lru連結清單分為熱資料區和冷資料區,熱資料區主要存放那些通路頻率⾼的緩存⻚,冷資料區存放通路頻率較低的緩存頁;從磁盤加載資料到lru連結清單時,⾸先會将加載到的緩存頁直接先放 到冷資料鍊的表頭,如果1000ms(預設,可配置)後冷資料的緩存頁又被通路了,此時就認 為這些1000ms之後被通路的緩存頁,在不久的未來可能還會被通路,可以認為它們是熱資料 了,就會把這些緩存頁從冷資料區的連結清單給移動到熱資料區連結清單的表頭,通過該步驟可以将熱 資料從冷資料堆中給巧妙的分離出來。

mysql高頻面試題,一文詳解Innodb存儲引擎原理

髒頁什麼時候會被刷入磁盤?

引入了 Buffer Pool 後,當修改資料時,首先是修改 Buffer Pool 中資料所在的頁,然後将其頁設定為髒頁,但是磁盤中還是原資料。

是以,髒頁需要被刷入磁盤,保證緩存和磁盤資料一緻,但是若每次修改資料都刷入磁盤,則性能會很差,是以一般都會在一定時機進行批量刷盤。

InnoDB 的更新操作采用的是 Write Ahead Log 政策,即先寫日志,再寫入磁盤,通過 redo log 日志讓 MySQL 擁有了崩潰恢複能力

為此,innodb設計了flush連結清單,在緩沖池中被更新過資料的緩存頁,這些緩存⻚的描述資訊都會被添加到flush連結清單中。

mysql高頻面試題,一文詳解Innodb存儲引擎原理

下面幾種情況會觸發髒頁的重新整理:

  • 當 redo log 日志滿了的情況下,會主動觸發髒頁重新整理到磁盤;
  • Buffer Pool 空間不足時,需要将一部分資料頁淘汰掉,如果淘汰的是髒頁,需要先将髒頁同步到磁盤;
  • MySQL 認為空閑時,背景線程會定期将适量的髒頁刷入到磁盤;
  • MySQL 正常關閉之前,會把所有的髒頁刷入到磁盤;

繼續閱讀