聚簇索引是對磁盤上實際數據從新組織以按指定的一個或多個列的值排序的算法。特色是存儲數據的順序和索引順序一致。通常狀況下主鍵會默認建立聚簇索引,且一張表只容許存在一個聚簇索引。html
在《數據庫原理》一書中是這麼解釋聚簇索引和非聚簇索引的區別的:聚簇索引的葉子節點就是數據節點,而非聚簇索引的葉子節點仍然是索引節點,只不過有指向對應數據塊的指針。mysql
所以,MYSQL中不一樣的數據存儲引擎對聚簇索引的支持不一樣就很好解釋了。下面,咱們能夠看一下MYSQL中MYISAM和INNODB兩種引擎的索引結構。算法
如原始數據爲:sql
MyISAM引擎的數據存儲方式如圖:數據庫
MYISAM是按列值與行號來組織索引的。它的葉子節點中保存的其實是指向存放數據的物理塊的指針。從MYISAM存儲的物理文件咱們能看出,MYISAM引擎的索引文件(.MYI)和數據文件(.MYD)是相互獨立的。緩存
而InnoDB按聚簇索引的形式存儲數據,因此它的數據佈局有着很大的不一樣。它存儲數據的結構大體以下:服務器
注:聚簇索引中的每一個葉子節點包含主鍵值、事務ID、回滾指針(rollback pointer用於事務和MVCC)和餘下的列(如col2)。佈局
INNODB的二級索引與主鍵索引有很大的不一樣。InnoDB的二級索引的葉子包含主鍵值,而不是行指針(row pointers),這減少了移動數據或者數據頁面分裂時維護二級索引的開銷,由於InnoDB不須要更新索引的行指針。其結構大體以下:post
INNODB和MYISAM的主鍵索引與二級索引的對比:性能
InnoDB的的二級索引的葉子節點存放的是KEY字段加主鍵值。所以,經過二級索引查詢首先查到是主鍵值,而後InnoDB再根據查到的主鍵值經過主鍵索引找到相應的數據塊。而MyISAM的二級索引葉子節點存放的仍是列值與行號的組合,葉子節點中保存的是數據的物理地址。因此能夠看出MYISAM的主鍵索引和二級索引沒有任何區別,主鍵索引僅僅只是一個叫作PRIMARY的惟1、非空的索引,且MYISAM引擎中能夠不設主鍵。
聚簇索引並非一種單獨的索引類型,而是一種數據存儲方式。具體的細節依賴於其實現方式,但innoddb 的聚簇索引實際上在同一個結構中保存了B-Tree索引和數據行。
當表有聚簇索引時,它的數據實際上存放在索引的葉子頁(leaf page)中。術語‘聚簇’表示數據行和相鄰的鍵值進錯的存儲在一塊兒。由於沒法同時把數據行存放在兩個不一樣的地方,因此在一個表中只能有一個聚簇索引 (不過,覆蓋索引能夠模擬多個聚簇索引的狀況)。
由於存儲引擎負責實現索引,所以不是全部的存儲引擎都支持聚簇索引。
一些數據庫服務器容許選擇哪一個索引做爲聚簇索引,但直到本書協寫做以前,尚未任何一個MySQL內奸的存儲引擎支持這一點。InnoDb將經過主鍵彙集數據。
若是沒有定義主鍵,InnoDB 會選擇一個惟一的非空索引代替。若是沒有這樣的索引,InnoDB 會隱式定義一個主鍵來做爲聚簇索引。InnoDB值彙集在同一個頁面中的記錄。。包含相鄰鍵值的頁面可能會相距很遠。
聚簇索引可能對性能有幫助,但也可能致使嚴重的性能問題。因此須要諮詢的考慮聚簇索引,尤爲是將表的存儲引擎從InnoDB 該成其餘的引擎的時候(返回來也同樣)。
聚簇索引的一些重要優勢:
能夠吧相關的數據保存在一塊兒。例如,實現電子郵箱時,能夠根據用戶id來彙集數據這樣只須要從磁盤讀取少數的數據頁就能獲取某個用戶的所有郵件。若是沒有使用聚簇索引,則每封郵件都肯能致使一次io。
數據訪問更快。聚簇索引將索引和數據保存在同一個B-Tree中,所以從聚簇索引中獲取數據一般比非聚簇索引中快。
使用覆蓋索引掃描的查詢能夠直接使用頁節點中的主鍵值。
聚簇索引的缺點:
聚簇索引最大限度的提升了io密集型應用的性能,但若是數據所有存放在內存中,則訪問的順序就沒那麼重要了,聚簇索引也就沒有什麼優點了。
插入速度嚴重依賴插入順序。按照主鍵的順序插入是加載數據到innodb表中速度最快的方式。但若是不是按照主鍵順序加載數據,那麼加載完成後最好使用OPTIMIZE TABLE 命令來從新組織一下表。
更新聚簇索引的代價很高,由於會強制InooDB將每一個更新的數據移動到新的位置。
基於聚簇索引的表在插入行,或者主鍵被更新致使須要移動行的時候,可能面臨’頁分裂(page split)‘的問題。當行的主鍵值要求必須將這一行插入到某個已滿的頁中時。存儲引擎,存儲引擎會將該頁分裂成兩個頁面來容納該行,這就是一次頁分裂操做。頁分裂會致使表佔用更多的存儲空間。
聚簇索引可能致使全表掃描變慢,尤爲是行比較稀疏,或者因爲頁分裂致使數據存儲不連續的時候。
二級索引(非聚簇索引)可能比想象的要更大,由於在二級索引的子節點包含了最優一個幾點可能讓人有些疑惑,爲何二級索引須要兩次索引查找?答案在於二級索引中保存的「行指針」的實質。要記住,二級索引葉子節點保存的不是隻想物理位置的指針,而是行的主鍵值。
這意味着經過二級索引進行查找行,存儲引擎須要找到二級索引的子節點得到對應的主鍵值,而後根據這個值去聚簇索引總超找到對應的行。這裏作了重複的工做:兩次B-Tree查找,而不是一次。對於InnoDB,自適應哈希索引可以減小這樣重複工做。
InnoDB 和 MyISAM的數據分佈對比
聚簇索引和非聚簇索引的數據分佈有區別,以及對應的主鍵索引和二級索引的數據分佈也有區別,一般會讓人感到困惑和意外。來看看InnoDB和MyISAM是如何存儲下面的這個表的:
CREATE TABLE layout_test(
col1 int not null,
col2 int not null,
primary key (col1),
key(col2)
);
假設該表的主鍵取值爲1-1w,按照隨機順序插入,並使用OPTIMIZE TABLE命令作了優化。換句話說,數據在磁盤的存儲方式已經最優,但進行的順序是隨機的。列col2的值時從1-100之間隨機賦值,因此有不少重複的值。
MyISAM 的數據分佈.。 MyISAM的數據分佈很是簡單,因此先介紹它。MyIsam按照數據插入的順序存儲在磁盤上。
實際上,MyISAM 中主鍵索引和其餘索引在結構上沒有什麼不一樣。主鍵索引就是一個名爲PRIMARY的惟一非空索引。
InnoDB 的數據分佈。由於InnoDB支持聚簇索引,索引使用很是不一樣的方式存儲一樣的數據。在InnoDB中,聚簇索引「就是」表,因此不像myISAM那樣須要獨立的行存儲。聚簇索引的每個葉子節點都包含了主鍵值、事務id,用於事務和MVCC的回滾指針。這樣的策略減小了當前出現行移動或者數據頁分裂是二級索引的維護工做。使用主鍵值看成指針會讓二級索引佔用更多的存儲空間,存儲,換來的好處是,InnoDB在移動行時,無需更新二級索引中的這個指針。InnoDB 的非葉子節點包含了索引列和一個紙箱下級節點的指針(下級節點能夠是葉子節點,也能夠是非葉子節點)。這對聚簇索引和二級索引都使用。
在InnoDB表中按照主鍵順序插入行
若是正在使用InnoDB 表而且沒有什麼數據須要彙集,那麼能夠定義一個代理鍵(surrogate key)做爲主鍵,這種主鍵的數據應該和應用無關,組件的的方法是使用AUTO_INCREMENT自增列。這樣能夠保證數據行是按照順序寫入,對於根據主鍵作關聯的操做性能也會更好。
最好避免隨機的(不連續,且值的分佈範圍很是大的)聚簇索引,特別是對於io密集型的應用。例如,從性能的角度考慮,使用UUID來做爲聚簇索引則會很糟糕:它使得聚簇索引的插入變得徹底隨機,這是最壞的狀況,使得數據沒有任何彙集特性。
由於主鍵的值時順序的,索引InnoDB 把每一條記錄都存儲在上一條記錄的後面。當達到頁的最大填充因子時(InnoDB 默認的最大填充因子是頁大小的15/16 ,留出部分空間用於之後修改),下一條記錄就會寫入到新的頁中。一旦數據按照這種順序的方式加載,主鍵頁就會近似於被順序的記錄填滿,這也正是所指望的結果(然而二級索引頁可能不同)。
使用UUID聚簇索引的表插入數據,由於新的行的主鍵值不必定比以前插入的大,因此InnoDB 沒法簡單的老是把新行插入到索引的最後,而是須要爲新的行尋找到合適的位置--一般是已有數據的中間位置--而且分配空間。這會增長不少的額外操做。並致使數據分佈不夠優化。下面是總結的一些缺點:
寫入的目標頁可能已經數到磁盤上並從緩存中移除,或者是尚未被加載到緩存中,InnoDB在插入以前不得不先找到並從磁盤讀取目標頁到內存中。這將致使大量的磁盤io。
由於寫入是亂序的,InnoDB 不得不頻繁的作分頁操做,以便爲新的行分配空間。頁分裂會致使移動大量數據,一次插入最少須要修改三個頁面,而不是一個頁。
因爲頻繁的頁分裂,頁會變得稀疏,而且被不規則的填充,因此最終數據會有碎片。
總結:使用InnoDB 時應該儘量地按照主鍵順序插入數據,而且儘量地使用單調增長的聚簇鍵的值來插入新行。