扒一扒InnoDB資料在硬碟上是如何存放的
前言
hello,小夥伴們下午好。俗話說的好,一時拖更一時爽,一直拖更一直爽。但是今天良心發現了,決定要更一下。高能預警,為了還債,特地寫了篇長文。
索引組織表
在InnoDB儲存引擎中,表都是按照主鍵順序
組織存放的,這種儲存方式的表被稱為索引組織表。
在InnoDB中,每張表都有各自的主鍵(Primary Key),如果在建立表的時候顯式的定義主鍵,則InnoDB儲存引擎會按如下方式選擇或建立主鍵。
- 首先判斷表中是否有非空的索引,如果有則第
一個定義的非空索引
作為主鍵 - 如果不符合上述條件,InnoDB儲存引擎自動建立一個6個位元組大小的指標
1.選擇第一個定義的非空索引
首先,我們建立表student,並填充兩條測試資料,語句如下:
create table student(
a int,b int not null,c int not null,UNIQUE key (a),UNIQUE key (c),UNIQUE key (b)
);
insert into student select 1,2,3;
insert into student select 4,5,6;複製程式碼
執行結果如下,我們可以看出_rowid的值等於列c的值,那就說明當前儲存的結構是將c作為主鍵的。另外a是可以為空的,雖然他定義唯一鍵的是第一個,但仍然不會作為主鍵。b雖然是先定義列,但是定義唯一鍵是在c之後,所以也不會被作為唯一鍵。
2.自動建立6個位元組大小的指標
首先,我們建立表score,並填充兩條測試資料,語句如下:
create table score(
a int,b int,c int,UNIQUE key (b)
);
insert into score select 1,3;
insert into score select 4,6;複製程式碼
執行結果如下,直接報錯了,不認為_rowid是他的列名,因為沒有滿足條件的列能成為他的主鍵,所以其就自動建立指標來解決問題啦。
InnoDB的邏輯儲存結構(整體)
表空間
表空間可以看做是InnoDB儲存引擎邏輯結構的最高層,所以的資料都存放在表空間裡面。
在預設情況下,InnoDB儲存引擎有一個共享表空間ibdata1,即所有資料都存放在這個表空間裡面。當然也可以在my.ini引數檔案中啟用innodb_file_per_table,則每張表的資料就可以單獨放在一個表空間中。
注意:即使啟用innodb_file_per_table引數,一些回滾資訊,系統事務資訊等還是在共享的表空間中,其隨著時間的變化大小還是會不斷增大的。單獨的表空間只會存放一些資料及索引資訊。
段
在InnoDB儲存引擎中,對段的管理都是由引擎自身所完成的,DBA不能也沒必要對其進行控制。
區
區是由連續頁組成的空間,在任何情況下每個區的大小都是1MB,頁的大小為16kb,所以一個區一共有64個連續的頁。
頁
下面詳細描述。
行
下面詳細描述。
InnoDB行記錄格式(重點)
InnoDB儲存引擎和大多數資料庫一樣,記錄是以行的形式儲存的,這就意味著頁中儲存著表中的一行行資料。那麼問題就來了,他這一行資料是包括哪些部分,除了具體的資料,還有其他的一些額外資訊嗎?
首先,我們先來看一下如果沒有指定行格式,其預設的行格式是什麼?
新建表test,擁有的欄位a,b ,c 。
create table test(
a varchar(10),b varchar (10) not null,c char(10)
);
insert into test values( '001','Andy','compter');
insert into test values( '002','Bob',NULL);複製程式碼
如下圖所示,預設的行格式是Compact ,該行格式是在MySQL5.0中引入的,其設計目標是高校的儲存資料。簡單來說,一個頁存放的行資料越多,其效能越高。
針對這個描述,咱先放在一邊,之後看到其他的行格式,咱對比著看,為啥compact效能高?
下圖為行格式Compact的大概結構,先瞅一眼,主要分為兩個部分,額外資訊和真實資料。額外資訊包括變成欄位長度,NULL值列表,記錄頭資訊,真實資料即為該行記錄有多少列,每列資料有哪些。其中記錄頭資訊包括記錄刪除位,記錄型別,下一個指標的位置。
注意,記錄頭資訊還有很多為其他資訊,但是重要的就這幾個。是不是不知道他們是幹撒的,一臉懵逼中,沒事,這還是混個臉熟,下一部分來慢慢盤他。
變長欄位長度
MySQL支援一些變長的資料型別,如varchar,text,blob,變長長度儲存多少位元組的資料是不固定的,所以我們在儲存真實資料的時候,需要將這些資料佔用的位元組數也要儲存起來。
剛才我們新增了兩條資料,先拿第一個資料為例,將真正資料佔用的位元組長度都存放在記錄的開頭部位,從而形成一個變長欄位長度列表,逆序
存放。如下圖,所以最終第一條記錄存放的十六進位制為08 04 03,他們之間沒有空格,是為了顯示的效果才加了空格。那第二條記錄很明顯是03 03.
注意:如果表中沒有變長欄位,則該欄位不存在。
NULL值列表
我們知道表中的某些列可能儲存NULL值,如果這些NULL值放在記錄的真實資料中儲存會佔用空間,所以Compact將這些值為NULL的列統一管理起來,儲存在NULL表中。
注意:跟變長欄位一樣,如果表中沒有NULL值的列,則該欄位不存在。
注意:MySQL規定NULL值列表必須是整數個位元組的位表示,如果使用的二進位制位歌手不是整數個位元組,則在位元組的高位補0.
第一行資料雖然沒有NULL值,但是a,c是可能儲存NULL值的列,所以NULL值列表如下,0表示列所對應的值不為NULL,1表示列所對應的值為NULL。
第二行資料a不是NULL,c是NULL,所以對應的NULL值列表如下。
記錄頭資訊
- 記錄刪除位(delete_mask):0為未刪除,1為刪除
- 記錄型別(record_type):0表示普通記錄,1表示B+樹非葉子節點記錄,2表示最小記錄,3表示最大記錄。
- 下一個指標的位置(next_record):表示從當前記錄的真實資料到下一條記錄的真實資料之間的
地址偏移量
。比如第一條記錄的next_record為20,那麼意味從第一條記錄的真實資料的地址處向後找32個位元組便是下一條記錄的真實資料。實際上就是連結串列
結構。
真實資料
InnoDB資料頁結構(重點)
下圖為資料頁的整體結構,咱先了解個大概,再慢慢盤他。
檔案頭(File Header)
頁的一些通用資訊,包括該頁屬於哪個表空間,InnoDB儲存引擎頁的型別。
頁頭(Page Header)
記錄資料頁的狀態資訊。
最小記錄+最大記錄(Infimum+supermum)
在InnoDB儲存引擎中,每個資料頁都有兩條虛擬的行記錄,用來限定記錄的邊界。
Infimum記錄是指比該頁中任何主鍵值都要小的值,Supermum記錄是指比該頁中任何主鍵值都要大的值。
這兩個值在頁建立時都會被的建立,並且在任何情況下不會被刪除。
這兩條記錄的構造十分簡單,都是有5個位元組大小的記錄頭資訊和8個位元組的固定部分組成的。
注意:上面提到了最小記錄的record_type為2,最大記錄的record_type為3。
使用者記錄(User Records)
重點來了,這邊是資料實際儲存位置,上面已經針對行格式做了詳細的劃分,現在咱就長話短說啦。
如果我刪除了第二行記錄,這條記錄並不是立刻刪除了,只是將刪除記錄位改為1啦。並且將他前面一條資料的指標指向他後面一條資料的地址,從而跳過這一條資料。
至於為什麼會這樣做呢?是為了節約時間和空間的消耗。如果在刪除的時候,立刻從磁碟上移除,那麼其他記錄在磁碟上重新排列需要效能消耗,所以在刪除的時候,只會將所有被刪除的記錄組成一個垃圾連結串列,稍後操作。或者有新紀錄插入的時候,覆蓋掉剛才的儲存空間。
空閒空間(Free Space)
不重要。
頁面目錄(Page Directory)
我們現在已經找到記錄在頁面中按照主鍵由小到達順序組成一個單連結串列,那如果想根據主鍵查詢頁中的某條記錄怎麼辦?
最蠢的方法肯定是按單連結串列的順序從頭到尾的查詢,因為只有知道前面一條記錄的記錄的地址,才能根據指標找到下一條記錄。但是這個有個明顯的缺點,就是太慢了,如果有1000條資料,一個個的查詢,如果最後一條記錄才滿足條件,那就太浪費時間啦。
我們可以先從順序表中想想,如果順序表中要找一個記錄,我們除了從頭開始查之外,還可以採用二分法,可以提升查詢速度。
那麼在單連結串列中是否可以採用二分法呢?答案是肯定的。即採用目錄的形式,將所有的記錄劃分為多個記錄塊,然後取每個記錄塊的最大的值,將其組成一個目錄,在查詢的時候,先查目錄,能判斷在哪個區間內。這個過程就類似於在書中找到某一個概念,要從目錄先找一樣。
文章尾部(File Tailer)
不重要。哈哈哈。
結束
碼字不易,請多多關注哦。
寫在最後
該篇借鑑的博文,書籍包括如下,特此感謝!
《MySQL技術內幕——InnoDB儲存引擎》
MySQL是如何執行的