推薦閱讀:
建表及插入數據
聚簇索引和非聚簇索引的數據分佈有區別,以及對應的主鍵索引和二級索引的數據分佈也有區別。下面我們看 InnoDB 和 MyISAM 是如何存儲下面這個表:
DROP TABLE IF EXISTS `test`; -- 若表已存在,則刪除
CREATE TABLE test (
id INT NOT NULL,
num int NOT NULL,
tag CHAR(12) NOT NULL,
PRIMARY KEY(id), -- 主鍵索引
KEY(num) -- 輔助索引
-- 嘗試不同的引擎 MyISAM InnoDB
) ENGINE=MyISAM;
複製代碼
然後插入下面的數據,id從1-9,故意把插入順序打亂,tag代表插入順序
INSERT test(id, num, tag) VALUES(7, 1, 'no.1');
INSERT test(id, num, tag) VALUES(5, 19, 'no.2');
INSERT test(id, num, tag) VALUES(6, 7, 'no.3');
INSERT test(id, num, tag) VALUES(9, 5, 'no.4');
INSERT test(id, num, tag) VALUES(3, 13, 'no.5');
INSERT test(id, num, tag) VALUES(4, 17, 'no.6');
INSERT test(id, num, tag) VALUES(2, 3, 'no.7');
INSERT test(id, num, tag) VALUES(8, 23, 'no.8');
INSERT test(id, num, tag) VALUES(1, 11, 'no.9');
複製代碼
MyISAM的數據存儲
MyISAM按照數據插入的順序存儲在磁盤上,我們假設第一行數據在數據庫表文件中的偏移位置是1,以此類推。 如下圖
現在我們驗證一下,使用 SELECT * FROM test;
掃描全表(SELECT * 不會使用任何索引,可以用 explain
實際觀察下),輸出的順序正是我們sql插入的順序,大家可以調整sql的順序再次插入,觀察輸出順序。
搞清楚了MyISAM的數據存儲方式,再來看下主鍵索引的存儲。我們假設每個磁盤塊只能存儲兩個節點數據,MyISAM的主鍵分佈如下圖:
其實MyISAM的二級索引和主鍵索引並無區別,只是名稱不同罷了,二級索引key(num)分佈如下圖:
二級索引子節點也只存儲了索引列(num)和文件的偏移量(P),但可以看出num是有序的,這在範圍查找(比如:where num > 3
)是非常有利的,但文件的偏移量並沒有規律,當需要回表查詢其他字段,可能會導致多次隨機I/O。
當對增加數據時MyISAM直接添加到文件尾部,不需要移動其他數據,而且更新主鍵時,也不會導致其他行數據移動,但它不支持行級鎖,修改時直接鎖表。若不需要事務支持,對讀多寫少的場景可以考慮MyISAM引擎,但不要默認使用MyISAM引擎。
InnoDB的數據存儲
InnoDB支持聚簇索引,所以使用非常不同的方式存儲同樣的數據。InnoDB數據存儲方式如下圖:
注意整個樹和MyISAM的主鍵索引非常類似,唯一的區別是葉子節點,InnoDB的葉子節點存儲了整個行數據(id、num、tag
),而不是隻有索引。聚簇索引“就是”整個表,而不像MyISAM那樣需要單獨的行存儲文件。當更新主鍵id時,可能會導致數據行移動,因爲索引和數據是存儲在一起的。若主鍵id是亂序寫入的,InnoDB不得不做頁分裂操作時,至少會導致修改三個頁(分裂產生的兩個頁,以及他們的父節點頁面),而不是一個頁,這和MyISAM新增數據時添加到文件尾部很不一樣。所以使用InnoDB引擎儘可能的按主鍵順序插入數據。
InnoDB的二級索引和聚簇索引也不一樣。InnoDB二級索引的葉子節點中存儲的不是行數據的“偏移量”,而是主鍵值。這意味着通過二級索引查找行數據,存儲引擎需要找到二級索引的葉子節點獲得對應的主鍵值,然後根據這個值去聚簇索引中查找到對應的行數據。這裏做了重複的工作:兩次B-Tree查找,而不是一次。這樣的策略減少了當出現行移動或者數據頁分裂時二級索引的維護工作。二級索引的數據分佈如下圖:
如果沒有定義主鍵,InnoDB會選擇一個唯一的非空索引代替。如果沒有這樣的索引InnoDB會隱式的定義一個主鍵作爲聚簇索引。選擇主鍵最好是自增id,如果採用字符串當做主鍵,不僅僅會增加聚簇索引的大小,也會增加二級索引的大小(二級索引葉子節點包含主鍵值
),並且字符串匹配需要一個個字符判斷,更加降低索引的查找性能。當然某些場景使用字符串作爲主鍵帶來的優勢要比這些損失更有利,需要根據場景具體評估。