<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          下次面試我一定問:MySql數(shù)據(jù)是如何存儲(chǔ)在磁盤上存儲(chǔ)的?

          共 6004字,需瀏覽 13分鐘

           ·

          2021-01-26 19:30

          △Hollis, 一個(gè)對(duì)Coding有著獨(dú)特追求的人△
          這是Hollis的第?330?篇原創(chuàng)分享
          作者 l?zyz1992
          來源 l Hollis(ID:hollischuang)
          本文來自作者投稿,原作者:zyz1992
          關(guān)于MySql數(shù)據(jù)庫(kù),相信很多人都不陌生,這是當(dāng)今最常用的一種關(guān)系型數(shù)據(jù)庫(kù),關(guān)于MySql的知識(shí)也是很豐富的。
          那么,不知道大家有沒有想過這樣的問題:MySql中的數(shù)據(jù)是存在哪的?又是如何存儲(chǔ)的呢?
          本文就來深入分析一下這些問題。文章內(nèi)容很長(zhǎng),建議收藏,建議大家靜下心來仔細(xì)閱讀,一定會(huì)有收獲!



          Innodb的存儲(chǔ)格式
          我們知道,關(guān)于Mysql這種關(guān)系型數(shù)據(jù)庫(kù),里面保存的數(shù)據(jù)最終都是要持久化到磁盤文件上面的。磁盤文件里存放的物理格式就是數(shù)據(jù)頁(yè)(關(guān)于數(shù)據(jù)頁(yè),如果不太理解先忽略,后續(xù)文章單獨(dú)介紹),數(shù)據(jù)頁(yè)中存放的是一行一行的記錄,但是對(duì)于數(shù)據(jù)頁(yè)中的每一行數(shù)據(jù)他又是怎么存儲(chǔ)的呢?
          我們拿Mysql中最常用的Innodb引擎來重點(diǎn)說,介紹下存儲(chǔ)格式是怎樣的。
          MySQL中存儲(chǔ)有3種:
          1. server層格式:與存儲(chǔ)引擎無關(guān),Binlog存儲(chǔ)常用的一種 (Bin Log 我們前面已經(jīng)詳細(xì)介紹過了,這個(gè)是MySql主從復(fù)制的一個(gè)很重要的文件)
          2. 索引元組格式:InnoDB存取過程記錄的中間狀態(tài),是InnoDB在內(nèi)存中存儲(chǔ)的格式 (換句話說我們的增刪改的操作都是在內(nèi)存中執(zhí)行的,這個(gè)只是一種臨時(shí)狀態(tài))
          3. 物理存儲(chǔ)格式:記錄在物理頁(yè)面中的存儲(chǔ)格式,即compact格式,與索引元組格式一一對(duì)應(yīng)。(這個(gè)是數(shù)據(jù)在磁盤存儲(chǔ)的真正的格式)
          MySql 的 InnoDB 存儲(chǔ)引擎和大多數(shù)數(shù)據(jù)庫(kù)一樣,都是以行的形式存儲(chǔ)數(shù)據(jù)的,我們可以通過SHOW TABLE STATUS查看到行的的存儲(chǔ)格式。
          InnoDB 儲(chǔ)存引擎支持有四種行儲(chǔ)存格式:COMPACT、Redundant、Dynamic 和 COMPRESSED。默認(rèn)為COMPACT。
          其他的參數(shù)我們這里不關(guān)注,僅僅看 Row_format 這列,這里我們可以看到行的存儲(chǔ)格式是 Compact,Compact 存儲(chǔ)數(shù)據(jù)的格式大致如下這樣
          對(duì)于我們看到的每一行數(shù)據(jù),我們最先看到的好像并不是各個(gè)列,而是一些類似列的描述信息。沒錯(cuò),其實(shí)在存儲(chǔ)的時(shí)候都會(huì)有一些都字段來描述這一行的信息,這就好比緩存池中的描述緩存頁(yè)的描述數(shù)據(jù)類似。
          上面的圖片大家可以這么簡(jiǎn)化來對(duì)待,事務(wù)ID回滾指針大家先不要關(guān)注,免得因?yàn)檫@個(gè)產(chǎn)生干擾而難于理解



          變長(zhǎng)字段 varchar 是如何存儲(chǔ)的
          一般情況下,我們要存儲(chǔ)的數(shù)據(jù)是并不能確定他的長(zhǎng)度的,大部分情況下都是一些變長(zhǎng)的數(shù)據(jù),以varchar為例,假設(shè)現(xiàn)在三個(gè)字段,字段類型分別為:varchar(10),char(1),char(1)char大家都是知道的,存儲(chǔ)的基本是一些已知的長(zhǎng)度固定的數(shù)據(jù),假設(shè)這三個(gè)類型的字段分別有如下的數(shù)據(jù):
          第一行:mysql a a;第二行:dog b c;畫個(gè)圖來幫助大家想象,現(xiàn)在你看到的是數(shù)據(jù)中為我們展現(xiàn)的樣子。
          但是在磁盤中可不是這樣子的,前文已經(jīng)提到過,表空間和行這些其實(shí)是邏輯上的概念,而數(shù)據(jù)頁(yè)是一種物理概念,也就是說我們看到的樣子在磁盤中的樣子本本是不一樣的。
          在磁盤中這兩條記錄大致是這樣子的:mysql a a dog b c,他們?cè)诖疟P中都是挨在一起存儲(chǔ)的。
          是不是瞬間感覺想要去查找一條數(shù)據(jù)非常麻煩,告訴你:是的,所以 MySql在設(shè)計(jì)的時(shí)候才會(huì)使用行格式存儲(chǔ),才會(huì)有前面的哪些變長(zhǎng)字段列表和標(biāo)志位以及記錄信息,這些就是用來記錄一行的記錄的信息,換句話說,MySql是通過這些描述信息來定位到一行中的具體記錄的。
          以第一行記錄為例,它在磁盤中的記錄情況大致是下面這樣子的,首先我們需要明確知道的是各個(gè)字段的類型MySql是很清楚的,在這個(gè)基礎(chǔ)上我們能看明白下面和想通后面的事情。首先我們看到 mysql是5個(gè)字符,使用十六進(jìn)制表示是 0x05,所以他的存儲(chǔ)大概是這樣子的:
          同理第二行數(shù)據(jù)類似這樣子的:
          相信大家在看到這里已經(jīng)大概能推測(cè)出MySql這個(gè)時(shí)候是怎么讀讀取數(shù)據(jù)的了,就是他會(huì)先根據(jù)變長(zhǎng)字段長(zhǎng)度列表中描述的變長(zhǎng)字段的信息去查找變長(zhǎng)字段,例如第一行,MySql解析到變長(zhǎng)字段是5,所以他會(huì)在mysql a a dog b c 這些里面取出5個(gè)字符,也就是 mysql,緊接著后面是兩個(gè) char(1) 也就是兩個(gè) a 在依次取出來。
          中間設(shè)備。由淺入深,我們慢慢來,剛剛上面說到的僅僅是一種非常簡(jiǎn)單的情況,這個(gè)首先是幫助大家理解,讓大家先明白有這么個(gè)回事,是這么回事,然后在慢慢的挖掘,我們一定要一個(gè)蘿卜一個(gè)坑的去踏實(shí)學(xué)習(xí)
          現(xiàn)在如果是多個(gè)varchar類型的字段怎么辦?例如:varchar(3),varchar(10),varchar(4),char(1),他有一條記錄是這樣子的:aaa ,bb,cccc,d,你根據(jù)上面的能推測(cè)出磁盤中的行記錄是怎么樣子的嗎?
          你是不是這么想的:磁盤中肯定是這樣的:0x03,0x02,0x04 null標(biāo)志位 記錄頭信息 aaa bb cccc d;這么想的同學(xué)請(qǐng)鼻子靠墻:);實(shí)際上并不是這樣子的。
          當(dāng)有多個(gè)變長(zhǎng)字段的時(shí)候,MySql在 compact 行格式中,把所有變長(zhǎng)類型的長(zhǎng)度存放在行記錄的開頭部位形成一個(gè)列表(這個(gè)列表就是剛剛上面說的變長(zhǎng)字段列表),按照列的逆序存放,也就是大致是這樣子的:
          這里我必須要給大家解釋下變長(zhǎng)字段列表會(huì)逆序存放,因?yàn)槊啃杏涗浀亩加幸粋€(gè) next_record指針 指向下一行 記錄頭信息真實(shí)數(shù)據(jù) 之間的位置。因?yàn)檫@個(gè)位置剛剛好,向左讀取就是行描述相關(guān)信息,向右讀取就是真實(shí)數(shù)據(jù)。正好對(duì)應(yīng)變長(zhǎng)字段長(zhǎng)度列表。畫個(gè)圖來幫助大家理解下:
          說到這里我們來稍微小結(jié)一下



          MySql中數(shù)據(jù)在磁盤的存儲(chǔ)小結(jié)
          1. 數(shù)據(jù)在磁盤中的存儲(chǔ)在物理空間上面是連續(xù)的
          2. 數(shù)據(jù)是被存放在MySql設(shè)計(jì)出來的數(shù)據(jù)頁(yè)上面的,數(shù)據(jù)頁(yè)上面存儲(chǔ)的才是最終的一行一行的記錄
          3. 行的存儲(chǔ)格式默認(rèn)是Compact
          4. 每一行數(shù)據(jù)都會(huì)有相應(yīng)的行描述部分,描述部分有【變長(zhǎng)字段列表】【NULL標(biāo)志位】【記錄頭信息】
          5. 每一行都會(huì)有next_record指針,指向記錄頭和變長(zhǎng)字段列表的中間某個(gè)位置,方便尋址
          6. 變長(zhǎng)列表中的varchar列的描述是逆序的(和字段的順序相反)這樣做的目的在上圖中描述的很清楚了



          NULL字段是如何存儲(chǔ)的
          上面說到了情況都是比較正常的情況,也就說上面提到的字段是沒有空值的,不管是變長(zhǎng)字段還是char字段,都是有值的,那如果某個(gè)字段允許為空,且值確實(shí)為空,MySql又是怎么處理的呢?是不是直接存儲(chǔ)NULL呢。
          假設(shè)MySql針對(duì)與Null直接存儲(chǔ),他實(shí)際上是按照“NULL”這樣字符串的形式存儲(chǔ)的,這樣顯然不行啊,因?yàn)樽址加每臻g的?。ㄒ粋€(gè) NULL 字符串要占用四個(gè)字符呢),你都沒有值,還占這么多空間,所以MySql肯定不是這樣存儲(chǔ)的。其實(shí)MySql在處理NULL值的時(shí)候是會(huì)將它通二進(jìn)制來存儲(chǔ)的,且也是逆序的



          MySql是如何通過二進(jìn)制來存儲(chǔ)NULL值的?
          上面的 Compact 格式數(shù)據(jù)中的【NULL標(biāo)志位(也可以叫NULL列表)】就是用來存儲(chǔ)NULL值的。若有某個(gè)字段值為 null,將將其 bit 位置為 1 說明值為 NULL,bit為 0 說明該字段值不為空
          是不是聽了解釋還是稀里糊涂的,別急,我畫個(gè)圖再來詳細(xì)介紹下,先假設(shè)我們有一張 sutdents 表

          CREATE?TABLE?`students`?(

          ??`name`?varchar(10)?NOT?NULL,

          ??`address`?varchar(255)?DEFAULT?NULL,

          ??`gender`?char(1)?DEFAULT?NULL,

          ??`class`?varchar(10)?DEFAULT?NULL,

          ??`hobbies`?varchar(255)?DEFAULT?NULL,

          ??PRIMARY?KEY?(`name`)

          )?

          他有這樣一行記錄
          我們先看變長(zhǎng)字段列表部分(記住是逆序存放的):
          roles是長(zhǎng)度為5記作:0x05;address 為null,不放在變長(zhǎng)列表中、gender 是 char 類型,不放在變長(zhǎng)列表中、class為空,不放在變長(zhǎng)列表中、hobby_xx長(zhǎng)度為8記作:0x08;所以變長(zhǎng)列表的記錄為:0x08 0x05
          現(xiàn)在到了NULL標(biāo)志位了:依舊是從右往左記錄字段:name 在設(shè)計(jì)的時(shí)候就是 not null,所示是不會(huì)出現(xiàn)在NULL標(biāo)志為中(Null標(biāo)志為是用來記錄字段可為NULL的字段,字段不可以為NULL的不是會(huì)被記錄到NULL標(biāo)志位的),address為NULL記作1,gender不為null記作0,class 為null記作1,hobbies不為null記作0;所以按照字段的順序結(jié)果就是:0101,但是NULL標(biāo)志位是逆序的,所以NULL標(biāo)志位存放的結(jié)果大概是這樣子的:0101,高位補(bǔ)0即可
          我們來模擬讀取下這條記錄:MySql 對(duì)于字段的類型一定是已知的(這個(gè)是在創(chuàng)建數(shù)據(jù)表的時(shí)候就已經(jīng)定下來了),所以對(duì)于 name 這種 not nul l的字段是不會(huì)去存放在null標(biāo)志位的,下面是詳細(xì)的讀取步驟:
          • name字段是主鍵,不可能在NULL 標(biāo)志位中的,又因?yàn)?name 是varchar 字段,所以就會(huì)去變長(zhǎng)字段列中查找,找到值為 0x05 接著就會(huì)去字段列表中讀取5個(gè)字符的長(zhǎng)度,也就是 roles ,第一個(gè)字段讀取成功;
          • 接著是 address 字段,因?yàn)轭愋褪?MySql 已知的,又因?yàn)樽侄沃禐?null 所以就不需要去讀取了,第二個(gè)字段讀取結(jié)束;
          • 接著是gender字段,是char類型的,直接拿到 f 就可以了;
          • 下一個(gè)是class 字段,因?yàn)槭莕ull 所以根本不會(huì)去變長(zhǎng)字段中查找;
          • 最后一個(gè)是 hobbies 字段,因?yàn)椴粸閚ull ,又是第二個(gè)變長(zhǎng)字段,這個(gè)時(shí)候就會(huì)去 變長(zhǎng)字段列表中查找,結(jié)果定位到是 0x08 那就讀取 8 個(gè)字符的長(zhǎng)度出來,拿出來是hobby_xx;
          說到這里,關(guān)于一行記錄的中的變長(zhǎng)字段列表和 NULL 標(biāo)志位具體是如何讀取字段值的就給大家介紹完了,不知道大家看到以上內(nèi)容腦子是不是會(huì)展現(xiàn)一條條行記錄的描述信息。目前我們只需要了解 varchar 和 NULL 存儲(chǔ)的基本就足夠了,因?yàn)檫@兩個(gè)表特殊,也是最經(jīng)常使用的,其他的字段類型本篇暫且不展開討論了。
          上面的記錄頭的信息我們還沒有討論過,下面我們?cè)僭敿?xì)介紹下記錄頭信息是什么。



          記錄頭信息
          記錄頭信息由40位的bit位組成,其各個(gè)位的劃分和含義如下:

          記錄頭的各個(gè)位的作用其實(shí)就已經(jīng)說的很清楚了,一些概念現(xiàn)在還沒法講解,很多東西需要到索引的時(shí)候才能展開講,這里大家需要明確的就是各個(gè)標(biāo)志位的含義。
          我認(rèn)為對(duì)于記錄頭的了解到這里就足夠了,各個(gè)標(biāo)志位的含義明確了到這個(gè)程度就行了,至于更多的可能我們根本接觸不到。這一小節(jié)就當(dāng)是科普。



          數(shù)據(jù)在磁盤上到底是怎么存儲(chǔ)的
          上面畫過這樣一張圖:
          之前說的是數(shù)據(jù)大致是這樣子在磁盤中存儲(chǔ)的:0x03 NULL標(biāo)志位 記錄頭信息 dog b c,但是實(shí)際上后面的列的數(shù)據(jù)并是不是我們看到的這個(gè)樣子,磁盤在存儲(chǔ)的時(shí)候是根據(jù)數(shù)據(jù)庫(kù)指定的字符集編碼存儲(chǔ)起來的你以為可能是上面那樣子存儲(chǔ)的。
          實(shí)際上可能是在樣子的:0x03 NULL 標(biāo)志位 記錄頭信息 1233 323 223,也就是說實(shí)際的數(shù)據(jù)在磁盤上存儲(chǔ)根本不是我們?nèi)四苷J(rèn)識(shí)的,后面的 1233 323 223 這幾個(gè)是我亂寫的,沒什么含義,主要是想表明是計(jì)算在實(shí)際存儲(chǔ)的時(shí)候是以特定的字符編碼來存儲(chǔ)的。
          另外每一行數(shù)據(jù)在被存儲(chǔ)的時(shí)候?qū)嶋H上還會(huì)有隱藏的字段,相信大家對(duì)這個(gè)應(yīng)該不會(huì)陌生的,row_id 大家應(yīng)該是知道的,哪怕自己沒用過可能也是聽過的,這個(gè)是數(shù)據(jù)庫(kù)自己為我們的每一行記錄生成的一個(gè)唯一的表示,如果我們沒有為數(shù)據(jù)表指定主鍵字段,也沒有指定 Unique key,那么這個(gè)時(shí)候數(shù)據(jù)庫(kù)內(nèi)部會(huì)幫我們維護(hù)一個(gè)自增長(zhǎng)的 ROW_ID 字段作為主鍵。
          還有一個(gè)隱藏字段就是 事務(wù)ID 上面的第二張圖上層畫出來過,這個(gè)顧名思義了,就是和事務(wù)相關(guān)的一個(gè)字段屬性字段名為DB_TRX_ID,這個(gè)再詳解到事務(wù)的時(shí)候再詳細(xì)介紹;最后一個(gè)也是在上面的第二張圖上畫出來了,就是回滾指針 DB_ROLL_PTR,回滾也是事務(wù)使用到的概念,也是放在事務(wù)那邊跟大家介紹
          現(xiàn)在再來整體回顧下一行記錄在磁盤中的存儲(chǔ)的結(jié)構(gòu)大概是什么樣子的:
          0x08 0x05 00000101 0000010100000000000000000000000000000010 21134 44 232343
          說到了存儲(chǔ),我們順便聊聊和存儲(chǔ)相關(guān)的一個(gè)概念,行溢出。



          行溢出
          說到這里,不知道大家有沒有想過一個(gè)問題,就是我們一直在說 MySql 存儲(chǔ)是以數(shù)據(jù)頁(yè)的形式來存儲(chǔ)的,然后數(shù)據(jù)頁(yè)中記錄的是一行行的記錄,但是往往常規(guī)情況下不會(huì)有什么問題。
          但是如果現(xiàn)在有一行記錄非常大,因?yàn)閿?shù)據(jù)頁(yè)大小默認(rèn)也就是16KB,假設(shè)某張表里面有text字段也有BLOB字段,且這一行的記錄的大小遠(yuǎn)遠(yuǎn)超過了一個(gè)數(shù)據(jù)頁(yè)的大小16KB,這種情況稱之為行溢出。
          MySql 是怎么來處理這種行溢出的情況的呢?實(shí)際上很簡(jiǎn)單,一個(gè)數(shù)據(jù)頁(yè)不夠就使用多個(gè)數(shù)據(jù)頁(yè),數(shù)據(jù)頁(yè)和數(shù)據(jù)頁(yè)之間使用鏈表連起來,之所以能夠使用鏈表連接因?yàn)閿?shù)據(jù)頁(yè)里面是包含了存放指針的 bit 位。對(duì)于行溢出的概念了解到這個(gè)程度就足夠了。我們學(xué)習(xí)是有的放矢,不是什么都要去刨根問底的。



          結(jié)束語(yǔ)
          本片文章詳細(xì)的介紹了 MySql 存儲(chǔ)數(shù)據(jù)的格式和數(shù)據(jù)具體在磁盤中是怎么存儲(chǔ)的,被存儲(chǔ)的數(shù)據(jù)又是怎么查找的,說白了很多事情都是已經(jīng)是既定的規(guī)則,所謂既定的規(guī)則就是很對(duì)東西已經(jīng)被更早的設(shè)計(jì)出來。

          所以你在使用和了解的使用只需要按照被人的規(guī)則來執(zhí)行,然后在此基礎(chǔ)上深入了解下別人為什么這么設(shè)計(jì)?這樣會(huì)更有助于我們掌握和理解某個(gè)知識(shí)點(diǎn)。


          往期推薦

          特普朗任期最后一天特赦了一位硅谷工程師,免去牢獄之災(zāi)和2億美金賠款


          鄭爽和張恒糾紛的前因后果:一個(gè)APP引發(fā)的血案


          被讀者投訴抄襲了!?



          ?

          直面Java第343期:為什么TOMCAT要破壞雙親委派

          深入并發(fā)第013期:拓展synchronized——鎖優(yōu)化


          如果你喜歡本文,
          請(qǐng)長(zhǎng)按二維碼,關(guān)注?Hollis.
          轉(zhuǎn)發(fā)至朋友圈,是對(duì)我最大的支持。

          點(diǎn)個(gè)?在看?
          喜歡是一種感覺
          在看是一種支持
          ↘↘↘
          瀏覽 14
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  可以免费观看的黄色视频 | 夜夜嗨AV一区二区三区网页 | 在线免费观看ww视频 | 黄色成人网站在线观看 | 超碰国产在线凹凸 |