表空間
我們腦子里理解數據的存儲就是‘一個庫里面有一些表,表里面有很多字段,然后有很多行數據’,但其實這只是我們的邏輯概念,數據在磁盤物理存儲方式可不是這樣的。mysql在服務器上是以一個文件夾的形式出現的,你創建了test數據庫,那么在磁盤上就會出現test的目錄,而創建的那些表,其實都是有一個表空間的概念,在磁盤都會對應一個表名.ibd數據文件。
extent
表空間的磁盤文件里有很多個數據頁,為了管理,表空間里又引入了一個數據區(extent)的概念。每個數據區大小是1mb,一個數據頁只有16kb、所以數據區對應着64個連續數據頁,然后256個數據區被划分為一組。對於表空間而言,他的每個數據組的第一個數據區的前3個數據頁,都是固定的,存放表空間和數據頁相關描述信息的其他數據區的前兩個數據頁,也是放一些相關屬性描述的。
數據頁
在研究 buffer pool 的時候我們知道是以數據頁為單位加載數據的,同樣也是以頁為單位將數據刷盤到磁盤的。 那這個數據頁到底長什么樣子呢?它其實拆分成了很多個部分,大體來說包含了文件頭、數據頁頭、最小記錄和最大記錄、多個數據行、空閑空間、數據頁目錄、文件尾部。
文件頭占據了38個字節,數據頁頭占據了56個字節,最大記錄和最小記錄占據了26個字節,數據行區域的大小是不固定的,空閑區域的大小也是不固定的,數據頁目錄的大小也是不固定的,然后文件尾部占據8個字節。
一行數據是如何存儲的
行格式
對於數據頁中的每一行數據存儲,這里涉及到"行格式"的概念,就是我們可以對一個表指定它的行存儲的格式是什么樣,既可以在建表時候指定、也可以后續修改存儲格式,這里我以COMPACT為例,除了這個還有其他集中存儲的格式,基本都大同小異。那比如我現在這里有一張表,五個字段分別為name、address、genderjob、school,就代表了客戶的姓名、地址、性別、工作以及學校。
CREATE TABLE customer ( name VARCHAR(10) NOT NULL, address VARCHAR(20), gender CHAR(1), job VARCHAR(30), school VARCHAR(50) ) ROW_FORMAT=COMPACT;
compact的存儲格式大概分為4部分 變長字段的長度列表,null值列表,數據頭,具體每一列數據。現在有這么一行數據“jack NULL m NULL xx_school”,它的存儲格式大概長下面這樣,接下來我會逐步分析每個字段的具體含義。
0x09 0x04 00000101 0000000000000000000010000000000000011001 616161 636320 6262626262
變長字段列表
mysql中有些字段長度是固定的,有些長度是不固定的,比如varchar這種不固定的,就叫變長字段。比如現在表里的五個字段值為 hello hi b word a 那么此時你要讀取第一個字段的值,那么第一個字段是變長的,到底他的實際長度是多少呢?此時你會發現第一行數據的開頭有一個變長字段的長度列表,里面會讀取到一個0x05這個十六進制的數字,發現第一個變長字段的長度是5,於是按照長度為5,讀取出來第一個字段的值,就是“hello”,同樣的后面分別為 0x02 0x04 0x01 就取出了 hi word a ,至於長度固定了的char(1)就直接取出來了 b 。只是變長字段列表是逆序存儲的!也就是 0x02 0x04 0x01 null值列表 數據頭 hello hi b word a。
null值列表
null值列表,顧名思義,說的就是你一行數據里可能有的字段值是null,比如你有一個name字段且允許為null的,那么實際上在存儲的時候,如果你沒給他賦值,他這個字段的值就是null。比如現在表中一行記錄為“jack NULL m NULL xx_school”,他的5個字段里有兩個字段都是NULL。我們知道對於變長字段會記錄在變長字段列表里面,但是這里要區分一下,那就是如果這個變長字段的值是NULL,就不用在變長字段長度列表里 存放他的值長度了,所以在上面那行數據中,只有name和school兩個變長字段是有值的,把他們的長度按照逆序放在變長字段長度列表中就可以了,如下所示:0x09 0x04 NULL值列表 頭信息 column1=value1 column2=value2 ... columnN=valueN
null值在磁盤上,是通過二進制的bit位來存儲的,如果bit值是1說明是NULL,如果bit值是0說明不是NULL。比如上面4個字段都允許為NULL,每個人都會有一個bit位,這一行數據的值是“jack NULL m NULL xx_school”,所以4個bit位應該是:1010;但實際他是按逆序放的,所以是:0101。而null值列表一般起碼是8個bit位的倍數,如果不足8個bit位就高位補0,所以實際存放看起來是如下的:0x09 0x04 00000101 數據頭 column1=value1 column2=value2 ... columnN=valueN
數據頭
數據頭是用來描述這行數據的,它只有40個bit位大小。
前兩個bit位都是預留位,沒有任何含義
第三個是delete_mask,用來標記這行數據是否被刪除了
第四個是min_rec_mask,他其實就是說在B+樹里每一層的非葉子節點里的最小值都有這個標記
接下來有4個bit位是n_owned,他其實就是記錄了一個記錄數
接着有13個bit位是heap_no,他代表的是當前這行數據在記錄堆里的位置
然后是3個bit位的record_type,這就是說這行數據的類型:0代表的是普通類型,1代表的是B+樹非葉子節點,2代表的是最小值數據,3代表的是最大值數據
最后是16個bit的next_record,這個是指向他下一條數據的指針
那么他真實存儲大致如下所示:
0x09 0x04 00000101 0000000000000000000010000000000000011001 jack m xx_school
數據值
我們已經知道他的變長字段的長度,用十六進制來存儲,然后是NULL值列表,指出了誰是NULL,接着是40個bit位的數據頭,然后是真實的數據值,就放在后面。在讀取這個數據的時候,他會根據變長字段的長度,先讀取出來jack這個值,因為他的長度是4,就讀取4個長度的數據,jack就出來了;然后發現第二個字段是NULL,就不用讀取了;第三個字段是定長字段,直接讀取1個字符就可以了,就是m這個值;第四個字段是NULL,不用讀取了;第五個字段是變長字段長度是9,讀取出來xx_school就可以了。
但實際上我們的數據都是進行編碼之后再存儲的,會在他真實數據部分加入一些隱藏字段:
首先有一個DB_ROW_ID字段,這就是一個行的唯一標識,是他數據庫內部給你搞的一個標識,不是你的主鍵ID字段。如果我們沒有指定主鍵和unique key唯一索引的時候,他就內部自動加一個ROW_ID作為主鍵。
接着是一個DB_TRX_ID字段,這是跟事務相關的,他是說這是哪個事務更新的數據,這是事務ID。
最后是DB_ROLL_PTR字段,這是回滾指針,是用來進行事務回滾的。
所以加上這幾個隱藏字段之后,實際一行數據可能看起來如下所示,這基本就是最終在磁盤上一行數據的樣子了:
0x09 0x04 00000101 0000000000000000000010000000000000011001 00000000094C(DB_ROW_ID)00000000032D(DB_TRX_ID) EA000010078E(DB_ROL_PTR) 616161 636320 6262626262
行溢出
我們說過一個數據頁是16kb,那如果一條數據不止16kb呢?比如 varchar(65532),或者text、blob 這種字段、是很可能溢出的,然后數據就會存儲在多個數據頁里。所以在存儲的數據中,會有一個指針將這些數據頁串聯起來。
。