本文來自做者投稿,原做者:zyz1992mysql
關於MySql數據庫,相信不少人都不陌生,這是當今最經常使用的一種關係型數據庫,關於MySql的知識也是很豐富的。sql
那麼,不知道你們有沒有想過這樣的問題:MySql中的數據是存在哪的?又是如何存儲的呢?數據庫
本文就來深刻分析一下這些問題。文章內容很長,建議收藏,建議你們靜下心來仔細閱讀,必定會有收穫!緩存
咱們知道,關於Mysql這種關係型數據庫,裏面保存的數據最終都是要持久化到磁盤文件上面的。磁盤文件裏存放的物理格式就是數據頁(關於數據頁,若是不太理解先忽略,後續文章單獨介紹),數據頁中存放的是一行一行的記錄,可是對於數據頁中的每一行數據他又是怎麼存儲的呢?學習
咱們拿Mysql中最經常使用的Innodb引擎來重點說,介紹下存儲格式是怎樣的。編碼
server層格式:與存儲引擎無關,Binlog存儲經常使用的一種 (Bin Log 咱們前面已經詳細介紹過了,這個是MySql主從複製的一個很重要的文件)設計
索引元組格式:InnoDB存取過程記錄的中間狀態,是InnoDB在內存中存儲的格式 (換句話說咱們的增刪改的操做都是在內存中執行的,這個只是一種臨時狀態)3d
物理存儲格式:記錄在物理頁面中的存儲格式,即compact格式,與索引元組格式一一對應。(這個是數據在磁盤存儲的真正的格式)指針
MySql 的 InnoDB 存儲引擎和大多數數據庫同樣,都是以行的形式存儲數據的,咱們能夠經過SHOW TABLE STATUS查看到行的的存儲格式。orm
InnoDB 儲存引擎支持有四種行儲存格式:COMPACT、Redundant、Dynamic 和 COMPRESSED。默認爲COMPACT。
其餘的參數咱們這裏不關注,僅僅看 Row_format 這列,這裏咱們能夠看到行的存儲格式是 Compact,Compact 存儲數據的格式大體以下這樣
對於咱們看到的每一行數據,咱們最早看到的好像並非各個列,而是一些相似列的描述信息。沒錯,其實在存儲的時候都會有一些都字段來描述這一行的信息,這就比如緩存池中的描述緩存頁的描述數據相似。
上面的圖片你們能夠這麼簡化來對待,事務ID和回滾指針你們先不要關注,省得由於這個產生干擾而難於理解
通常狀況下,咱們要存儲的數據是並不能肯定他的長度的,大部分狀況下都是一些變長的數據,以varchar爲例,假設如今三個字段,字段類型分別爲:varchar(10),char(1),char(1),char你們都是知道的,存儲的基本是一些已知的長度固定的數據,假設這三個類型的字段分別有以下的數據:
第一行:mysql a a;第二行:dog b c;畫個圖來幫助你們想象,如今你看到的是數據中爲咱們展示的樣子。
可是在磁盤中可不是這樣子的,前文已經提到過,表空間和行這些實際上是邏輯上的概念,而數據頁是一種物理概念,也就是說咱們看到的樣子在磁盤中的樣子本本是不同的。
在磁盤中這兩條記錄大體是這樣子的:mysql a a dog b c,他們在磁盤中都是挨在一塊兒存儲的。
是否是瞬間感受想要去查找一條數據很是麻煩,告訴你:是的,因此 MySql在設計的時候纔會使用行格式存儲,纔會有前面的哪些變長字段列表和標誌位以及記錄信息,這些就是用來記錄一行的記錄的信息,換句話說,MySql是經過這些描述信息來定位到一行中的具體記錄的。
以第一行記錄爲例,它在磁盤中的記錄狀況大體是下面這樣子的,首先咱們須要明確知道的是各個字段的類型MySql是很清楚的,在這個基礎上咱們能看明白下面和想通後面的事情。首先咱們看到 mysql是5個字符,使用十六進制表示是 0x05,因此他的存儲大概是這樣子的:
同理第二行數據相似這樣子的:
相信你們在看到這裏已經大概能推測出MySql這個時候是怎麼讀讀取數據的了,就是他會先根據變長字段長度列表中描述的變長字段的信息去查找變長字段,例如第一行,MySql解析到變長字段是5,因此他會在mysql a a dog b c 這些裏面取出5個字符,也就是 mysql,緊接着後面是兩個 char(1) 也就是兩個 a 在依次取出來。
中間設備。由淺入深,咱們慢慢來,剛剛上面說到的僅僅是一種很是簡單的狀況,這個首先是幫助你們理解,讓你們先明白有這麼個回事,是這麼回事,而後在慢慢的挖掘,咱們必定要一個蘿蔔一個坑的去踏實學習
如今若是是多個varchar類型的字段怎麼辦?例如:varchar(3),varchar(10),varchar(4),char(1),他有一條記錄是這樣子的:aaa,bb,cccc,d,你根據上面的能推測出磁盤中的行記錄是怎麼樣子的嗎?
你是否是這麼想的:磁盤中確定是這樣的:0x03,0x02,0x04 null標誌位 記錄頭信息aaa bb cccc d;這麼想的同窗請鼻子靠牆:);實際上並非這樣子的。
當有多個變長字段的時候,MySql在 compact 行格式中,把全部變長類型的長度存放在行記錄的開頭部位造成一個列表(這個列表就是剛剛上面說的變長字段列表),按照列的逆序存放,也就是大體是這樣子的:
這裏我必需要給你們解釋下變長字段列表會逆序存放,由於每行記錄的都有一個next_record指針 指向下一行 記錄頭信息和 真實數據 之間的位置。由於這個位置剛恰好,向左讀取就是行描述相關信息,向右讀取就是真實數據。正好對應變長字段長度列表。畫個圖來幫助你們理解下:
說到這裏咱們來稍微小結一下
數據在磁盤中的存儲在物理空間上面是連續的
數據是被存放在MySql設計出來的數據頁上面的,數據頁上面存儲的纔是最終的一行一行的記錄
行的存儲格式默認是Compact
每一行數據都會有相應的行描述部分,描述部分有【變長字段列表】【NULL標誌位】【記錄頭信息】
每一行都會有next_record指針,指向記錄頭和變長字段列表的中間某個位置,方便尋址
變長列表中的varchar列的描述是逆序的(和字段的順序相反)這樣作的目的在上圖中描述的很清楚了
上面說到了狀況都是比較正常的狀況,也就說上面提到的字段是沒有空值的,無論是變長字段仍是char字段,都是有值的,那若是某個字段容許爲空,且值確實爲空,MySql又是怎麼處理的呢?是否是直接存儲NULL呢。
假設MySql針對與Null直接存儲,他其實是按照「NULL」這樣字符串的形式存儲的,這樣顯然不行啊,由於字符串要佔用空間的啊(一個 NULL 字符串要佔用四個字符呢),你都沒有值,還佔這麼多空間,因此MySql確定不是這樣存儲的。其實MySql在處理NULL值的時候是會將它通二進制來存儲的,且也是逆序的
上面的 Compact 格式數據中的【NULL標誌位(也能夠叫NULL列表)】就是用來存儲NULL值的。如有某個字段值爲 null,將將其 bit 位置爲 1 說明值爲 NULL,bit爲 0 說明該字段值不爲空
是否是聽了解釋仍是稀裏糊塗的,別急,我畫個圖再來詳細介紹下,先假設咱們有一張 sutdents 表
CREATETABLE`students`(
`name`varchar(10)NOTNULL,
`address`varchar(255)DEFAULTNULL,
`gender`char(1)DEFAULTNULL,
`class`varchar(10)DEFAULTNULL,
`hobbies`varchar(255)DEFAULTNULL,
PRIMARYKEY(`name`)
)
他有這樣一行記錄
咱們先看變長字段列表部分(記住是逆序存放的):
roles是長度爲5記做:0x05;address 爲null,不放在變長列表中、gender 是 char 類型,不放在變長列表中、class爲空,不放在變長列表中、hobby_xx長度爲8記做:0x08;因此變長列表的記錄爲:0x08 0x05
如今到了NULL標誌位了:依舊是從右往左記錄字段:name 在設計的時候就是 not null,所示是不會出如今NULL標誌爲中(Null標誌爲是用來記錄字段可爲NULL的字段,字段不能夠爲NULL的不是會被記錄到NULL標誌位的),address爲NULL記做1,gender不爲null記做0,class 爲null記做1,hobbies不爲null記做0;因此按照字段的順序結果就是:0101,可是NULL標誌位是逆序的,因此NULL標誌位存放的結果大概是這樣子的:0101,高位補0便可
咱們來模擬讀取下這條記錄:MySql 對於字段的類型必定是已知的(這個是在建立數據表的時候就已經定下來了),因此對於 name 這種 not nul l的字段是不會去存放在null標誌位的,下面是詳細的讀取步驟:
name字段是主鍵,不可能在NULL 標誌位中的,又由於 name 是varchar 字段,因此就會去變長字段列中查找,找到值爲 0x05 接着就會去字段列表中讀取5個字符的長度,也就是 roles ,第一個字段讀取成功;
接着是 address 字段,由於類型是 MySql 已知的,又由於字段值爲 null 因此就不須要去讀取了,第二個字段讀取結束;
接着是gender字段,是char類型的,直接拿到 f 就能夠了;
下一個是class 字段,由於是null 因此根本不會去變長字段中查找;
最後一個是 hobbies 字段,由於不爲null ,又是第二個變長字段,這個時候就會去 變長字段列表中查找,結果定位到是 0x08 那就讀取 8 個字符的長度出來,拿出來是hobby_xx;
說到這裏,關於一行記錄的中的變長字段列表和 NULL 標誌位具體是如何讀取字段值的就給你們介紹完了,不知道你們看到以上內容腦子是否是會展示一條條行記錄的描述信息。目前咱們只須要了解 varchar 和 NULL 存儲的基本就足夠了,由於這兩個表特殊,也是最常用的,其餘的字段類型本篇暫且不展開討論了。
上面的記錄頭的信息咱們尚未討論過,下面咱們再詳細介紹下記錄頭信息是什麼。
記錄頭信息由40位的bit位組成,其各個位的劃分和含義以下:
記錄頭的各個位的做用其實就已經說的很清楚了,一些概念如今還無法講解,不少東西須要到索引的時候才能展開講,這裏你們須要明確的就是各個標誌位的含義。
我認爲對於記錄頭的瞭解到這裏就足夠了,各個標誌位的含義明確了到這個程度就好了,至於更多的可能咱們根本接觸不到。這一小節就當是科普。
上面畫過這樣一張圖:
以前說的是數據大體是這樣子在磁盤中存儲的:0x03 NULL標誌位 記錄頭信息 dog b c,可是實際上後面的列的數據並是否是咱們看到的這個樣子,磁盤在存儲的時候是根據數據庫指定的字符集編碼存儲起來的你覺得多是上面那樣子存儲的。
實際上多是在樣子的:0x03 NULL 標誌位 記錄頭信息 1233 323 223,也就是說實際的數據在磁盤上存儲根本不是咱們人能認識的,後面的 1233 323 223 這幾個是我亂寫的,沒什麼含義,主要是想代表是計算在實際存儲的時候是以特定的字符編碼來存儲的。
另外每一行數據在被存儲的時候實際上還會有隱藏的字段,相信你們對這個應該不會陌生的,row_id 你們應該是知道的,哪怕本身沒用過可能也是聽過的,這個是數據庫本身爲咱們的每一行記錄生成的一個惟一的表示,若是咱們沒有爲數據表指定主鍵字段,也沒有指定 Unique key,那麼這個時候數據庫內部會幫咱們維護一個自增加的 ROW_ID 字段做爲主鍵。
還有一個隱藏字段就是 事務ID 上面的第二張圖上層畫出來過,這個顧名思義了,就是和事務相關的一個字段屬性字段名爲DB_TRX_ID,這個再詳解到事務的時候再詳細介紹;最後一個也是在上面的第二張圖上畫出來了,就是回滾指針 DB_ROLL_PTR,回滾也是事務使用到的概念,也是放在事務那邊跟你們介紹
如今再來總體回顧下一行記錄在磁盤中的存儲的結構大概是什麼樣子的:
0x08 0x05 00000101 000001010000000000000000000000000000001021134 44 232343
說到了存儲,咱們順便聊聊和存儲相關的一個概念,行溢出。
說到這裏,不知道你們有沒有想過一個問題,就是咱們一直在說 MySql 存儲是以數據頁的形式來存儲的,而後數據頁中記錄的是一行行的記錄,可是每每常規狀況下不會有什麼問題。
可是若是如今有一行記錄很是大,由於數據頁大小默認也就是16KB,假設某張表裏面有text字段也有BLOB字段,且這一行的記錄的大小遠遠超過了一個數據頁的大小16KB,這種狀況稱之爲行溢出。
MySql 是怎麼來處理這種行溢出的狀況的呢?實際上很簡單,一個數據頁不夠就使用多個數據頁,數據頁和數據頁之間使用鏈表連起來,之因此可以使用鏈表鏈接由於數據頁裏面是包含了存放指針的 bit 位。對於行溢出的概念瞭解到這個程度就足夠了。咱們學習是有的放矢,不是什麼都要去刨根問底的。
本片文章詳細的介紹了 MySql 存儲數據的格式和數據具體在磁盤中是怎麼存儲的,被存儲的數據又是怎麼查找的,說白了不少事情都是已是既定的規則,所謂既定的規則就是很對東西已經被更早的設計出來。
因此你在使用和了解的使用只須要按照被人的規則來執行,而後在此基礎上深刻了解下別人爲何這麼設計?這樣會更有助於咱們掌握和理解某個知識點。
文章來源:https://mp.weixin.qq.com/s?__biz=MzI3NzE0NjcwMg==&mid=2650152173&idx=1&sn=649e69f288d3d529d3af5282584b97dc&chksm=f36801ccc41f88dae42bf2914ae341aca27ee1284d06b50e8801e261bcb20e0c8cc380194edc&scene=21#wechat_redirect