大家都知道 MySQL 的數據都是保存在磁碟的,那具體是保存在哪個文件呢?MySQL 存儲的行為是由存儲引擎實現的,MySQL 支持多種存儲引擎,不同的存儲引擎保存的文件自然也不同。InnoDB 是我們常用的存儲引擎,也是 MySQL 預設的存儲引擎。本文主要以 InnoDB 存儲引擎展開討論。 ...
前言
InnoDB簡介
InnoDB行格式
-- 創建數據表時,顯示指定行格式
CREATE TABLE 表名 (列的信息) ROW_FORMAT=行格式名稱;
-- 創建數據表時,修改行格式
ALTER TABLE 表名 ROW_FORMAT=行格式名稱;
-- 查看數據表的行格式
show table status like '<數據表名>';
compact格式
-
變長欄位長度列表中只存儲值為 非NULL 的列內容占用的長度,值為 NULL 的列的長度是不儲存的 。
-
並不是所有記錄都有這個 變長欄位長度列表 部分,比方說表中所有的列都不是變長的數據類型的話,這一部分就不需要有
-
首先統計表中允許存儲NULL的列有哪些。
-
根據列的實際值,用0或者1填充NULL值列表,1代表該列的值為空,0代表該列的值不為空。
-
如果表中沒有允許存儲 NULL 的列,則 NULL值列表 也不存在了。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
redundant 格式
dynamic 格式
compressed 格式
InnoDB數據頁結構
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
先創建一個表:
CREATE TABLE test(
a1 INT,
a2 INT,
a3 VARCHAR(100),
PRIMARY KEY (a1)
) CHARSET=ascii ROW_FORMAT=Compact;
test表中插入幾條記錄:
INSERT INTO test VALUES(1, 10, 'aaa');
INSERT INTO test VALUES(2, 20, 'bbb');
INSERT INTO test VALUES(3, 30, 'ccc');
INSERT INTO test VALUES(4, 40, 'ddd');
-
delete_mask這個屬性標記著當前記錄是否被刪除。這些被刪除的記錄之所以不立即從磁碟上移除,是因為移除它們之後把其他的記錄在磁碟上重新排列需要性能消耗,所以只是打一個刪除標記而已。所有被刪除掉的記錄都會組成一個所謂的垃圾鏈表,在這個鏈表中的記錄占用的空間稱之為所謂的可重用空間,之後如果有新記錄插入到表中的話,可能把這些被刪除的記錄占用的存儲空間覆蓋掉。
-
min_rec_maskB+樹的每層非葉子節點中的最小記錄都會添加該標記,min_rec_mask值都是0,意味著它們都不是B+樹的非葉子節點中的最小記錄。
-
n_owned在頁目錄分組時使用,每個組的最後一條記錄(也就是組內最大的那條記錄)的頭信息中的n_owned屬性表示該記錄擁有多少條記錄,也就是該組內共有幾條記錄。
-
heap_no這個屬性表示當前記錄在本頁中的位置,從圖中可以看出來,我們插入的4條記錄在本頁中的位置分別是:2、3、4、5。heap_no值為0和1的記錄,稱為偽記錄或者虛擬記錄。這兩個偽記錄一個代表最小記錄,一個代表最大記錄。
-
record_type這個屬性表示當前記錄的類型,一共有4種類型的記錄,0表示普通記錄,1表示B+樹非葉節點記錄,2表示最小記錄,3表示最大記錄。
-
next_record它表示從當前記錄的真實數據到下一條記錄的真實數據的地址偏移量。比方說第一條記錄的next_record值為32,意味著從第一條記錄的真實數據的地址處向後找32個位元組便是下一條記錄的真實數據。下一條記錄指得並不是按照我們插入順序的下一條記錄,而是按照主鍵值由小到大的順序的下一條記錄。而且規定Infimum記錄(也就是最小記錄) 的下一條記錄就是本頁中主鍵值最小的用戶記錄,而本頁中主鍵值最大的用戶記錄的下一條記錄就是 Supremum記錄(也就是最大記錄)。
Page Directory(頁目錄)
-
初始情況下一個數據頁里只有最小記錄和最大記錄兩條記錄,它們分屬於兩個分組。
-
之後每插入一條記錄,都會從頁目錄中找到主鍵值比本記錄的主鍵值大並且差值最小的槽,然後把該槽對應的記錄的n_owned值加1,表示本組內又添加了一條記錄,直到該組中的記錄數等於8個。
-
在一個組中的記錄數等於8個後再插入一條記錄時,會將組中的記錄拆分成兩個組,一個組中4條記錄,另一個5條記錄。這個過程會在頁目錄中新增一個槽來記錄這個新增分組中最大的那條記錄的偏移量。
INSERT INTO test VALUES(5, 50, 'eee');
INSERT INTO test VALUES(6, 60, 'fff');
INSERT INTO test VALUES(7, 70, 'ggg');
INSERT INTO test VALUES(8, 80, 'hhh');
INSERT INTO test VALUES(9, 90, 'iii');
INSERT INTO test VALUES(10, 100, 'jjj');
INSERT INTO test VALUES(11, 110, 'kkk');
INSERT INTO test VALUES(12, 120, 'lll');
這裡為了便於理解,圖中只保留了用戶記錄頭信息中的n_owned和next_record屬性。
因為各個槽代表的記錄的主鍵值都是從小到大排序的,所以我們可以使用二分法來進行快速查找。
所以在一個數據頁中查找指定主鍵值的記錄的過程分為兩步:
2.通過記錄的next_record屬性遍歷該槽所在的組中的各個記錄。
比方說我們查找主鍵值為x的記錄,計算中間槽的位置(min+max)/2 =mid,查看mid槽對應的主鍵值y,若x<y,則min不變,max=mid,若x>y,則max不變,min=mid。依此類推。
舉例:我們想找主鍵值為6的記錄,過程是這樣的計算中間槽的位置:(0+3)/2=1,所以查看槽1對應記錄的主鍵值為4,因為4 < 6,所以設置low=1,high保持不變。因為high - low的值為1,所以確定主鍵值為6的記錄在槽2對應的組中。我們可以很輕易的拿到槽1對應的記錄(主鍵值為4),該條記錄的下一條記錄就是槽2中主鍵值最小的記錄,該記錄的主鍵值為5。所以我們可以從這條主鍵值為5的記錄出發,遍歷槽2中的各條記錄找到主鍵為6 的數據。
註意:若查到數據在槽2的分組中,由於槽2是指向最後一個記錄,所以需要向上找一個槽位,定位到上一個槽位最後一行,然後再向下找。
File Header(文件頭部)
B+樹索引
-
以主鍵為搜索條件這個查找過程我們已經很熟悉了,可以在頁目錄中使用二分法快速定位到對應的槽,然後再遍歷該槽對應分組中的記錄即可快速找到指定的記錄。
-
以其他列作為搜索條件對非主鍵列的查找的過程可就不這麼幸運了,因為在數據頁中並沒有對非主鍵列建立所謂的頁目錄,所以我們無法通過二分法快速定位相應的槽。這種情況下只能從最小記錄開始依次遍歷單鏈表中的每條記錄,然後對比每條記錄是不是符合搜索條件。
1:定位到記錄所在的頁。
索引
test表中插入幾條記錄:
INSERT INTO test VALUES(1, 10, 'aa');
INSERT INTO test VALUES(2, 20, 'bb');
INSERT INTO test VALUES(4, 40, 'dd');
INSERT INTO test VALUES(3, 30, 'cc');
1:先從目錄項中根據二分法快速確定出主鍵值為5的記錄在目錄2中(因為 4 < 5 < 7),它對應的數據頁是頁23。
InnoDB中的索引方案
聚簇索引
1:使用記錄主鍵值的大小進行記錄和頁的排序
二級索引
-
使用記錄a2列的大小進行記錄和頁的排序
-
頁內的記錄是按照a2列的大小順序排成一個單向鏈表。
-
各個存放用戶記錄的頁也是根據頁中記錄的a2列大小順序排成一個雙向鏈表。
-
存放目錄項記錄的頁分為不同的層次,在同一層次中的頁也是根據頁中目錄項記錄的a2列大小順序排成一個雙向鏈表。
-
B+樹的葉子節點存儲的並不是完整的用戶記錄,而只是a2列+主鍵這兩個列的值。
-
目錄項記錄中不再是主鍵+頁號的搭配,而變成了a2列+頁號的搭配。
索引的代價
1:空間上的代價每建立一個索引都要為它建立一棵B+樹,每一棵B+樹的每一個節點都是一個數據頁,一個頁預設會占用16KB的存儲空間。
總結
本文來自博客園,作者:古道輕風,轉載請註明原文鏈接:https://www.cnblogs.com/88223100/p/How-much-do-you-know-about-MySQL-data-storage.html