MySQL探祕(三):InnoDB的記憶體結構和特性

程式設計師歷小冰發表於2018-08-26

 常言說得好,每個成功男人背後都有一個為他默默付出的女人,而對於MySQL來說,這個“人”就是InnoDB儲存引擎。

 MySQL區別於其他資料庫的最為重要的特點就是其外掛式的表儲存引擎。而在眾多儲存引擎中,InnoDB是最為常用的儲存引擎。從MySQL5.5.8版本開始,InnoDB儲存引擎是預設的儲存引擎。

 InnoDB儲存引擎支援事務,其設計目標主要面向線上事務處理(OLTP)的應用。其特點是行鎖設計、支援外來鍵,並支援非鎖定讀,即預設讀操作不會產生鎖。

 InnoDB通過使用多版本併發控制(MVCC)來獲取高併發性,並且實現了SQL標準的4中隔離級別,預設為REPEATABLE級別。同時,使用一種被稱為next-key-locking的策略來避免幻讀現象的產生。除此之外,InnoDB儲存引擎還提供了插入緩衝(insert buffer)、二次寫(double write)、自適應雜湊索引(adaptive hash index)、預讀(read ahead)等高效能和高可用的功能。

InnoDB整體架構.png
 上圖詳細顯示了InnoDB儲存引擎的體系架構,從圖中可見,InnoDB儲存引擎由記憶體池,後臺執行緒和磁碟檔案三大部分組成。接下來我們就來簡單瞭解一下記憶體相關的概念和原理。

緩衝池

 InnoDB儲存引擎是基於磁碟儲存的,並將其中的記錄按照頁的方式進行管理。但是由於CPU速度和磁碟速度之間的鴻溝,基於磁碟的資料庫系統通常使用緩衝池記錄來提高資料庫的的整體效能。

 在資料庫中進行讀取操作,首先將從磁碟中讀到的頁放在緩衝池中,下次再讀相同的頁中時,首先判斷該頁是否在緩衝池中。若在緩衝池中,稱該頁在緩衝池中被命中,直接讀取該頁。否則,讀取磁碟上的頁。

 對於資料庫中頁的修改操作,則首先修改在緩衝池中的頁,然後再以一定的頻率重新整理到磁碟上。頁從緩衝池重新整理回磁碟的操作並不是在每次頁發生更新時觸發,而是通過一種稱為CheckPoint的機制重新整理回磁碟。

 所以,緩衝池的大小直接影響著資料庫的整體效能,可以通過配置引數innodb_buffer_pool_size來設定。

 具體來看,緩衝池中快取的資料頁型別有:索引頁、資料頁、undo頁、插入緩衝(insert buffer)、自適應雜湊索引(adaptive hash index)、InnoDB儲存的鎖資訊(lock info)和資料字典資訊(data dictionary)。

 在架構圖上可以看到,InnoDB儲存引擎的記憶體區域除了有緩衝池之外,還有重做日誌緩衝和額外記憶體池。InnoDB儲存引擎首先將重做日誌資訊先放到這個緩衝區中,然後按照一定頻率將其重新整理到重做日誌檔案中。重做日誌緩衝一般不需要設定的很大,該值可由配置引數innodb_log_buffer_size控制。

資料頁和索引頁

 Page是Innodb儲存的最基本結構,也是Innodb磁碟管理的最小單位,與資料庫相關的所有內容都儲存在Page結構裡。Page分為幾種型別,資料頁和索引頁就是其中最為重要的兩種型別。

插入緩衝(Insert Buffer)

 我們都知道,在InnoDB引擎上進行插入操作時,一般需要按照主鍵順序進行插入,這樣才能獲得較高的插入效能。當一張表中存在非聚簇的且不唯一的索引時,在插入時,資料頁的存放還是按照主鍵進行順序存放,但是對於非聚簇索引葉節點的插入不再是順序的了,這時就需要離散的訪問非聚簇索引頁,由於隨機讀取的存在導致插入操作效能下降。

 InnoDB為此設計了Insert Buffer來進行插入優化。對於非聚簇索引的插入或者更新操作,不是每一次都直接插入到索引頁中,而是先判斷插入的非聚集索引是否在緩衝池中,若在,則直接插入;若不在,則先放入到一個Insert Buffer中。看似資料庫這個非聚集的索引已經查到葉節點,而實際沒有,這時存放在另外一個位置。然後再以一定的頻率和情況進行Insert Buffer和非聚簇索引頁子節點的合併操作。這時通常能夠將多個插入合併到一個操作中,這樣就大大提高了對於非聚簇索引的插入效能。

兩次寫(Double Write)

 如果說Insert Buffer給InnoDB儲存引擎帶來了效能上的提升,那麼Double Write帶給InnoDB儲存引擎的是資料頁的可靠性。

doublewrite示意圖

 如上圖所示,Double Write由兩部分組成,一部分是記憶體中的double write buffer,大小為2MB,另一部分是物理磁碟上共享表空間連續的128個頁,大小也為2MB。在對緩衝池的髒頁進行重新整理時,並不直接寫磁碟,而是通過memcpy函式將髒頁先複製到記憶體中的該區域,之後通過doublewrite buffer再分兩次,每次1MB順序地寫入共享表空間的物理磁碟上,然後馬上呼叫fsync函式,同步磁碟,避免作業系統緩衝寫帶來的問題。在完成doublewrite頁的寫入後,再講doublewirite buffer中的頁寫入各個表空間檔案中。

 如果作業系統在將頁寫入磁碟的過程中發生了崩潰,在恢復過程中,InnoDB儲存引擎可以從共享表空間中的doublewrite中找到該頁的一個副本,將其複製到表空間檔案中,再應用重做日誌。

重做日誌(Redo Log Buffer)

 當緩衝池中的頁的版本比磁碟要新時,資料庫需要將新版本的頁從緩衝池重新整理到磁碟。但是如果每次一個頁傳送變化,就進行重新整理,那麼效能開發是非常大的,於是InnoDB採用了Write Ahead Log策略,即當事務提交時,先寫重做日誌,然後再擇時將髒頁寫入磁碟。如果發生當機導致資料丟失,就通過重做日誌進行資料恢復。

InnoDB資料寫入示意圖

 InnoDB儲存引擎會首先將重做日誌資訊先放入重做日誌緩衝中,然後再按照一定頻率將其重新整理到重做日誌檔案。重做日誌緩衝一般不需要設定得很大,因為一般情況每一秒鐘都會講重做日誌緩衝重新整理到日誌檔案中。可通過配置引數innodb_log_buffer_size控制,預設為8MB。

 除了每秒重新整理機制之外,每次事務提交時重做日誌緩衝也會重新整理到日誌中。InnoDB是事務的儲存引擎,其通過Force Log at Commit機制實現事務的永續性,即當事務提交時,必須先將該事務的所有日誌寫入到重做日誌檔案進行持久化,然後事務的提交操作完成才算完成。InnoDB的寫入機制大致入下圖所示。

 為了確保每次日誌都寫入到重做日誌檔案,在每次講重做日誌緩衝寫入重做日誌後,必須呼叫一次fsync操作,將緩衝檔案從檔案系統快取中真正寫入磁碟。

 可以通過innodb_flush_log_at_trx_commit來控制重做日誌重新整理到磁碟的策略。該引數預設值為1,表示事務提交必須進行一次fsync操作,還可以設定為0和2。0表示事務提交時不進行寫入重做日誌操作,該操作只在主執行緒中完成,2表示提交時寫入重做日誌,但是隻寫入檔案系統快取,不進行fsync操作。由此可見,設定為0時,效能最高,但是喪失了事務的一致性。

自適應雜湊索引(Adaptive Hash Index)

 InnoDB會根據訪問的頻率和模式,為熱點頁建立雜湊索引,來提高查詢效率。InnoDB儲存引擎會監控對錶上各個索引頁的查詢,如果觀察到建立雜湊索引可以帶來速度上的提升,則建立雜湊索引,所以叫做自適應雜湊索引。

 自適應雜湊索引是通過緩衝池的B+樹頁構建而來,因此建立速度很快,而且不需要對整張資料表建立雜湊索引。其有一個要求,即對這個頁的連續訪問模式必須是一樣的,也就是說其查詢的條件(WHERE)必須完全一樣,而且必須是連續的。

鎖資訊(lock info)

 我們都知道,InnoDB儲存引擎會在行級別上對錶資料進行上鎖。不過InnoDB也會在資料庫內部其他很多地方使用鎖,從而允許對多種不同資源提供併發訪問。資料庫系統使用鎖是為了支援對共享資源進行併發訪問,提供資料的完整性和一致性。關於鎖的具體知識我們之後再進行詳細學習。

資料字典資訊(Data Dictionary)

 InnoDB有自己的表快取,可以稱為表定義快取或者資料字典。當InnoDB開啟一張表,就增加一個對應的物件到資料字典。

 資料字典是對資料庫中的資料、庫物件、表物件等的元資訊的集合。在MySQL中,資料字典資訊內容就包括表結構、資料庫名或表名、欄位的資料型別、檢視、索引、表欄位資訊、儲存過程、觸發器等內容。MySQL INFORMATION_SCHEMA庫提供了對資料局後設資料、統計資訊、以及有關MySQL server的訪問資訊(例如:資料庫名或表名,欄位的資料型別和訪問許可權等)。該庫中儲存的資訊也可以稱為MySQL的資料字典。

後記

 本篇文章只是簡單的介紹一下InnoDB記憶體相關的概念和原理,如果大家想要了解更多關於InnoDB的知識,請關注我的微信公眾號。

MySQL探祕(三):InnoDB的記憶體結構和特性

參考

  • 《MySQL技術內幕InnoDB儲存引擎》
  • 《高效能MySQL》
  • 姜承曉老師的InnoDB架構圖

相關文章