無水乾貨:InnoDB底層原理
很多文章都是直接開始介紹有哪些儲存引擎,並沒有去介紹儲存引擎本身。那麼究竟什麼是儲存引擎?不知道大家有沒有想過,MySQL是如何儲存我們丟進去的資料的? |
很多文章都是直接開始介紹有哪些儲存引擎,並沒有去介紹儲存引擎本身。那麼究竟什麼是儲存引擎?不知道大家有沒有想過,MySQL是如何儲存我們丟進去的資料的?
其實儲存引擎也很簡單,我認為就是一種儲存解決方案,實現了新增資料、更新資料和建立索引等等功能。
有哪些已有的儲存引擎可以讓我們選擇呢?
InnoDB、MyISAM、Memory、CSV、Archive、Blackhole、Merge、Federated、Example
種類很多,但是常用的儲存引擎目前就只有InnoDB和MyISAM,我也會著重來介紹這兩種儲存引擎。
InnoDB是目前使用最廣的MySQL儲存引擎,MySQL從5.5版本開始InnoDB就已經是預設的儲存引擎了。那你知道為什麼InnoDB被廣泛的使用呢?先把這個問題放一放,我們先來了解一下InnoDB儲存引擎的底層原理。
InnoDB的記憶體架構主要分為三大塊,緩衝池(Buffer Pool)、重做緩衝池(Redo Log Buffer)和額外記憶體池
InnoDB為了做資料的持久化,會將資料儲存到磁碟上。但是面對大量的請求時,CPU的處理速度和磁碟的IO速度之間差距太大,為了提高整體的效率, InnoDB引入了緩衝池。
當有請求來查詢資料時,如果快取池中沒有,就會去磁碟中查詢,將匹配到的資料放入快取池中。同樣的,如果有請求來修改資料,MySQL並不會直接去修改磁碟,而是會修改已經在緩衝池的頁中的資料,然後再將資料刷回磁碟,這就是緩衝池的作用,加速讀,加速寫,減少與磁碟的IO互動。
緩衝池說白了就是把磁碟中的資料丟到記憶體,那既然是記憶體就會存在沒有記憶體空間可以分配的情況。所以緩衝池採用了LRU演算法,在緩衝池中沒有空閒的頁時,來進行頁的淘汰。但是採用這種演算法會帶來一個問題叫做緩衝池汙染。
當你在進行批次掃描甚至全表掃描時,可能會將緩衝池中的熱點頁全部替換出去。這樣以來可能會導致MySQL的效能斷崖式下降。所以InnoDB對LRU做了一些最佳化,規避了這個問題。
MySQL採用日誌先行,在真正寫資料之前,會首先記錄一個日誌,叫Redo Log,會定期的使用CheckPoint技術將新的Redo Log刷入磁碟,這個後面會講。
除了資料之外,裡面還儲存了索引頁、Undo頁、插入緩衝、自適應雜湊索引、InnoDB鎖資訊和資料字典。下面選幾個比較重要的來簡單聊一聊。
插入緩衝針對的操作是更新或者插入,我們考慮最壞的情況,那就是需要更新的資料都不在緩衝池中。那麼此時會有下面兩種方案。
來一條資料就直接寫入磁碟
等資料達到某個閾值(例如50條)才批次的寫入磁碟
很明顯,第二種方案要好一點,減少了與磁碟IO的互動。
鑑於都聊到了插入緩衝,我就不得不需要提一嘴兩次寫,因為我認為這兩個InnoDB的特性是相輔相成的。
插入緩衝提高了MySQL的效能,而兩次寫則在此基礎上提高了資料的可靠性。我們知道,當資料還在緩衝池中的時候,當機器當機了,發生了寫失效,有Redo Log來進行恢復。但是如果是在從緩衝池中將資料刷回磁碟的時候當機了呢?
這種情況叫做部分寫失效,此時重做日誌就無法解決問題。
在刷髒頁時,並不是直接刷入磁碟,而是copy到記憶體中的Doublewrite Buffer中,然後再複製至磁碟共享表空間(你可以就理解為磁碟)中,每次寫入1M,等copy完成後,再將Doublewrite Buffer中的頁寫入磁碟檔案。
有了兩次寫機制,即使在刷髒頁時當機了,在例項恢復的時候也可以從共享表空間中找到Doublewrite Buffer的頁副本,直接將其覆蓋原來的資料頁即可。
自適應索引就跟JVM在執行過程中,會動態的把某些熱點程式碼編譯成Machine Code一樣,InnoDB會監控對所有索引的查詢,對熱點訪問的頁建立雜湊索引,以此來提升訪問速度。
你可能多次看到了一個關鍵字頁,接下來那我們就來聊一下頁是什麼?
頁,是InnoDB中資料管理的最小單位。當我們查詢資料時,其是以頁為單位,將磁碟中的資料載入到緩衝池中的。同理,更新資料也是以頁為單位,將我們對資料的修改刷回磁碟。每頁的預設大小為16k,每頁中包含了若干行的資料,頁的結構如下圖所示。
不用太糾結每個區是幹嘛的,我們只需要知道這樣設計的好處在哪兒。每一頁的資料,可以透過FileHeader中的上一下和下一頁的資料,頁與頁之間可以形成雙向連結串列。因為在實際的物理儲存上,資料並不是連續儲存的。你可以把他理解成G1的Region在記憶體中的分佈。
而一頁中所包含的行資料,行與行之間則形成了單向連結串列。我們存入的行資料最終會到User Records中,當然最初User Records並不佔據任何儲存空間。隨著我們存入的資料越來越多,User Records會越來越大,Free Space的空間會越來越小,直到被佔用完,就會申請新的資料頁。
User Records中的資料,是按照主鍵id來進行排序的,當我們按照主鍵來進行查詢時,會沿著這個單向連結串列一直往後找,
上面聊過,InnoDB中緩衝池中的頁資料更新會先於磁碟資料更新的,InnoDB也會採用日誌先行(Write Ahead Log)策略來重新整理資料,什麼意思呢?當事務開始時,會先記錄Redo Log到Redo Log Buffer中,然後再更新緩衝池頁資料。
Redo Log Buffer中的資料會按照一定的頻率寫到重做日誌中去。被更改過的頁就會被標記成髒頁,InnoDB會根據CheckPoint機制來將髒頁刷到磁碟。
上面提到了Redo log,這一小節就專門來講一講日誌,日誌分為如下兩個維度。
MySQL層面
InnoDB層面
MySQL的日誌可以分為錯誤日誌、二進位制檔案、查詢日誌和滿查詢日誌。
錯誤日誌 很好理解,就是服務執行過程中發生的嚴重錯誤日誌。當我們的資料庫無法啟動時,就可以來這裡看看具體不能啟動的原因是什麼
二進位制檔案 它有另外一個名字你應該熟悉,叫Binlog,其記錄了對資料庫所有的更改。
查詢日誌 記錄了來自客戶端的所有語句
慢查詢日誌 這裡記錄了所有響應時間超過閾值的SQL語句,這個閾值我們可以自己設定,引數為long_query_time,其預設值為10s,且預設是關閉的狀態,需要手動的開啟。
InnoDB日誌就只有兩種,Redo Log和Undo Log,
Redo Log 重做日誌,用於記錄事務操作的變化,且記錄的是修改之後的值。不管事務是否提交都會記錄下來。例如在更新資料時,會先將更新的記錄寫到Redo Log中,再更新快取中頁中的資料。然後按照設定的更新策略,將記憶體中的資料刷回磁碟。
Undo Log 記錄的是記錄的事務開始之前的一個版本,可用於事務失敗之後發生的回滾。
Redo Log記錄的是具體某個資料頁上的修改,只能在當前Server使用,而Binlog可以理解為可以給其他型別的儲存引擎使用。這也是Binlog的一個重要作用,那就是主從複製,另外一個作用是資料恢復。
上面提到過,Binlog中記錄了所有對資料庫的修改,其記錄日誌有三種格式。分別是Statement、Row和MixedLevel。
Statement 記錄所有會修改資料的SQL,其只會記錄SQL,並不需要記錄下這個SQL影響的所有行,減少了日誌量,提高了效能。但是由於只是記錄執行語句,不能保證在Slave節點上能夠正確執行,所以還需要額外的記錄一些上下文資訊
Row 只儲存被修改的記錄,與Statement只記錄執行SQL來比較,Row會產生大量的日誌。但是Row不用記錄上下文資訊了,只需要關注被改成啥樣就行。
MixedLevel 就是Statement和Row混合使用。
具體使用哪種日誌,需要根據實際情況來決定。例如一條UPDATE語句更新了很多的資料,採用Statement會更加節省空間,但是相對的,Row會更加的可靠。
由於MyISAM並不常用,我也不打算去深究其底層的一些原理和實現。我們在這裡簡單的對比一下這兩個儲存引擎的區別就好。我們分點來一點點描述。
事務 InnoDB支援事務、回滾、事務安全和奔潰恢復。而MyISAM不支援,但查詢的速度要比InnoDB更快
主鍵 InnoDB規定,如果沒有設定主鍵,就自動的生成一個6位元組的主鍵,而MyISAM允許沒有任何索引和主鍵的存在,索引就是行的地址
外來鍵 InnoDB支援外來鍵,而MyISAM不支援
表鎖 InnoDB支援行鎖和表鎖,而MyISAM只支援表鎖
全文索引 InnoDB不支援全文索引,但是可以用外掛來實現相應的功能,而MyISAM是本身就支援全本索引
行數 InnoDB獲取行數時,需要掃全表。而MyISAM儲存了當前表的總行數,直接讀取即可。
所以,簡單總結一下,MyISAM只適用於查詢大於更新的場景,如果你的系統查詢的情況佔絕大多數(例如報表系統)就可以使用MyISAM來儲存,除此之外,都建議使用InnoDB。
由於時間的原因,本文只是簡單的聊了聊InnoDB的整體架構,並沒有很深入的去聊某些點。例如InnoDB是如何改進來解決緩衝池汙染的,其演算法具體是什麼,checkpoint是如何工作的等等,只是做一個簡單的瞭解,之後如果有時間的話再細聊。
原文地址:
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31559985/viewspace-2717808/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- InnoDB索引與底層原理索引
- 簡單瞭解InnoDB底層原理
- 無水乾貨:Java陣列優秀指南Java陣列
- 【乾貨】MySQL底層架構設計,你瞭解多少?MySql架構
- OC底層探索(十六) KVO底層原理
- synchronized底層原理synchronized
- RunLoop底層原理探究OOP
- RabbitMq底層原理分析MQ
- HashMap原理底層剖析HashMap
- HashMap的底層原理HashMap
- iOS底層原理-CategoryiOSGo
- ArrayList集合底層原理
- ConcurrentHashMap底層原理HashMap
- 無水乾貨-如何快速分析Linux伺服器的效能問題Linux伺服器
- HashMap原理詳解,包括底層原理HashMap
- HashMap底層實現原理HashMap
- 理解PHP底層原理(一)PHP
- 底層原理面試彙總面試
- 初步理解 JavaScript 底層原理JavaScript
- Netty的底層原理Netty
- Spring Cloud底層原理SpringCloud
- NSDictionary底層實現原理
- Vue中的底層原理Vue
- ArrayList底層原理淺析
- HashMap的底層原理分析HashMap
- 底層原理探究(二)RunLoopOOP
- AutoreleasePool底層實現原理
- iOS底層原理探究-RunloopiOSOOP
- golang select底層原理Golang
- Volatile的底層原理
- MySQL底層概述—10.InnoDB鎖機制MySql
- iOS底層原理總結 – RunLoopiOSOOP
- [譯] SQLite 底層查詢原理SQLite
- volatile底層原理詳解
- php底層原理之函式PHP函式
- java學習----底層原理一Java
- AOP底層原理之CGlibCGLib
- MySQL索引底層實現原理MySql索引