文本已收錄至個人GitHub倉庫,歡迎Star:github.com/bin39232820…
種一棵樹最好的時間是十年前,其次是如今
我知道不少人不玩qq了,可是懷舊一下,歡迎加入六脈神劍Java菜鳥學習羣,羣聊號碼:549684836 鼓勵你們在技術的路上寫博客mysql
咱們繼續來探索mysql。前面咱們瞭解了mysql的索引的一些基礎知識,今天咱們來康康具體的InnoDB存儲引擎git
InnoDB是一個將表中的數據存儲到磁盤上的存儲引擎,因此即便關機後重啓咱們的數據仍是存在的。而真正處理數據的過程是發生在內存中的,因此須要把磁盤中的數據加載到內存中,若是是處理寫入或修改請求的話,還須要把內存中的內容刷新到磁盤上。而咱們知道讀寫磁盤的速度很是慢,和內存讀寫差了幾個數量級,因此當咱們想從表中獲取某些記錄時,InnoDB存儲引擎須要一條一條的把記錄從磁盤上讀出來麼?不,那樣會慢死,InnoDB採起的方式是:將數據劃分爲若干個頁,以頁做爲磁盤和內存之間交互的基本單位,InnoDB中頁的大小通常爲 16 KB。也就是在通常狀況下,一次最少從磁盤中讀取16KB的內容到內存中,一次最少把內存中的16KB內容刷新到磁盤中。github
這個意思就是咱們表中一行一行的數據格式,那麼這些一行一行的數據格式是怎麼樣的呢?sql
設計InnoDB存儲引擎的大佬們到如今爲止設計了4種不一樣類型的行格式,分別是Compact、Redundant、Dynamic和Compressed行格式,隨着時間的推移,他們可能會設計出更多的行格式,可是無論怎麼變,在原理上大致都是相同的。服務器
這邊就隨便了解一個就行了數據結構
從圖中能夠看出來 這種行格式分爲2個部分,第一個部分是記錄這一行的額外信息,第二個部分就是記錄的真實數據post
上面就是咱們一行數據裏面大概真實存放的東西了。接下來咱們來聊聊頁,多個行組成的頁。學習
索引頁表明的這塊16KB大小的存儲空間能夠被劃分爲多個部分,不一樣部分有不一樣的功能,各個部分如圖所示:優化
在頁的7個組成部分中,咱們本身存儲的記錄會按照咱們指定的行格式存儲到User Records部分。可是在一開始生成頁的時候,其實並無User Records這個部分,每當咱們插入一條記錄,都會從Free Space部分,也就是還沒有使用的存儲空間中申請一個記錄大小的空間劃分到User Records部分,當Free Space部分的空間所有被User Records部分替代掉以後,也就意味着這個頁使用完了,若是還有新的記錄插入的話,就須要去申請新的頁了,這個過程的圖示以下設計
爲了更好的管理在User Records中的這些記錄,InnoDB可費了一番力氣呢,在哪費力氣了呢?不就是把記錄按照指定的行格式一條一條擺在User Records部分麼?其實這話還得從記錄行格式的記錄頭信息中提及。
User Records 裏面存儲的就是咱們前面說的行格式的數據 下面咱們來講說它具體的東西吧
delete_mask 這個屬性標記着當前記錄是否被刪除,佔用1個二進制位,值爲0的時候表明記錄並無被刪除,爲1的時候表明記錄被刪除掉了。 當咱們下次再插入相同的數據的時候,會複用這個空間
min_rec_mask B+樹的每層非葉子節點中的最小記錄都會添加該標記
heap_no 這個屬性表示當前記錄在本頁中的位置
record_type 這個屬性表示當前記錄的類型,一共有4種類型的記錄,0表示普通記錄,1表示B+樹非葉節點記錄,2表示最小記錄,3表示最大記錄。
next_record 這玩意兒很是重要,它表示從當前記錄的真實數據到下一條記錄的真實數據的地址偏移量。比方說第一條記錄的next_record值爲32,意味着從第一條記錄的真實數據的地址處向後找32個字節即是下一條記錄的真實數據。若是你熟悉數據結構的話,就當即明白了,這實際上是個鏈表,能夠經過一條記錄找到它的下一條記錄。可是須要注意注意再注意的一點是,下一條記錄指得並非按照咱們插入順序的下一條記錄,而是按照主鍵值由小到大的順序的下一條記錄。並且規定 Infimum記錄(也就是最小記錄) 的下一條記錄就是本頁中主鍵值最小的用戶記錄,而本頁中主鍵值最大的用戶記錄的下一條記錄就是 Supremum記錄(也就是最大記錄) ,爲了更形象的表示一下這個next_record起到的做用,咱們用箭頭來替代一下next_record中的地址偏移量:
從上面咱們能夠看出 再頁裏面的行 其實就是一個鏈表,方便咱們去查詢遍歷。
這個幹嗎的呢,上面咱們說了 頁裏面的行能夠作成一個鏈表去查詢,可是要知道一個頁16k 能夠存儲的數據會特別多,因此呢若是隻是一個鏈表的話,那麼查詢的效率確定不高,因此mysql 把幾個行記錄分組一個組,一個組也叫一個槽,而後把這些槽用一個鏈表連起來,那麼下次去查詢的時候先去查槽,而後再去查組的這種形式。思想和跳錶的思想很像哈哈。
因此在一個數據頁中查找指定主鍵值的記錄的過程分爲兩步:
經過二分法肯定該記錄所在的槽,並找到該槽所在分組中主鍵值最小的那條記錄。
經過記錄的next_record屬性遍歷該槽所在的組中的各個記錄。
爲了能獲得一個數據頁中存儲的記錄的狀態信息,好比本頁中已經存儲了多少條記錄,第一條記錄的地址是什麼,頁目錄中存儲了多少個槽等等,特地在頁中定義了一個叫Page Header的部分,它是頁結構的第二部分,這個部分佔用固定的56個字節,專門存儲各類狀態信息。
上邊嘮叨的Page Header是專門針對數據頁記錄的各類狀態信息,比方說頁裏頭有多少個記錄了呀,有多少個槽了呀。咱們如今描述的File Header針對各類類型的頁都通用,也就是說不一樣類型的頁都會以File Header做爲第一個組成部分,它描述了一些針對各類頁都通用的一些信息,比方說這個頁的編號是多少,它的上一個頁、下一個頁是誰啦吧啦吧啦
這個我也稍微提一點 它裏面有存 FIL_PAGE_PREV和FIL_PAGE_NEXT,這個是什麼意思呢,
咱們前邊強調過,InnoDB都是以頁爲單位存放數據的,有時候咱們存放某種類型的數據佔用的空間很是大(比方說一張表中能夠有成千上萬條記錄),InnoDB可能不能夠一次性爲這麼多數據分配一個很是大的存儲空間,若是分散到多個不連續的頁中存儲的話須要把這些頁關聯起來,FIL_PAGE_PREV和FIL_PAGE_NEXT就分別表明本頁的上一個和下一個頁的頁號。這樣經過創建一個雙向鏈表把許許多多的頁就都串聯起來了,而無需這些頁在物理上真正連着。須要注意的是,並非全部類型的頁都有上一個和下一個頁的屬性,不過咱們嘮叨的數據頁(也就是類型爲FIL_PAGE_INDEX的頁)是有這兩個屬性的,因此全部的數據頁實際上是一個雙鏈表,就像這樣:
咱們知道InnoDB存儲引擎會把數據存儲到磁盤上,可是磁盤速度太慢,須要以頁爲單位把數據加載到內存中處理,若是該頁中的數據在內存中被修改了,那麼在修改後的某個時間須要把數據同步到磁盤中。可是在同步了一半的時候中斷電了咋辦,這不是莫名尷尬麼?爲了檢測一個頁是否完整(也就是在同步的時候有沒有發生只同步一半的尷尬狀況),設計InnoDB的大佬們在每一個頁的尾部都加了一個File Trailer部分。
總結
文章內容出自 MySQL 是怎樣運行的:從根兒上理解 MySQL,
咱們下章繼續再戰。
好了各位,以上就是這篇文章的所有內容了,能看到這裏的人呀,都是真粉。
創做不易,各位的支持和承認,就是我創做的最大動力,咱們下篇文章見
六脈神劍 | 文 【原創】若是本篇博客有任何錯誤,請批評指教,不勝感激 !