聚簇索引與非聚簇索引的區別

聚簇索引與非聚簇索引的區別
 在《數據庫原理》裏面,對聚簇索引的解釋是:聚簇索引的順序就是數據的物理存儲順序,而對非聚簇索引的解釋是:索引順序與數據物理排列順序無關。正式因爲如此,所以一個表最多只能有一個聚簇索引。
聚簇索引的葉節點就是數據節點,而非聚簇索引的葉節點仍然是索引節點,並保留一個鏈接指向對應數據塊
聚簇索引主鍵的插入速度要比非聚簇索引主鍵的插入速度慢很多。
相比之下,聚簇索引適合排序,非聚簇索引不適合用在排序的場合。因爲聚簇索引本身已經是按照物理順序放置的,排序很快。非聚簇索引則沒有按序存放,需要額外消耗資源來排序。
當你需要取出一定範圍內的數據時,用聚簇索引也比用非聚簇索引好。

索引有兩種類型:聚簇索引和非聚簇索引。
在聚簇索引中,索引樹的葉級頁包含實際的數據:記錄的索引順序與物理順序相同。
在非聚簇索引中,葉級頁指向表中的記錄:記錄的物理順序與邏輯順序沒有必然的聯繫。

聚簇索引非常象目錄表,目錄表的順序與實際的頁碼順序是一致的。非聚簇索引則更象書的標準索引表,索引表中的順序通常與實際的頁碼順序是不一致的。一本書也許有多個索引。例如,它也許同時有主題索引和作者索引。同樣,一個表可以有多個非聚簇索引。

通常情況下,你使用的是聚簇索引,但是你應該對兩種類型索引的優缺點都有所理解。

每個表只能有一個聚簇索引,因爲一個表中的記錄只能以一種物理順序存放。通常你要對一個表按照標識字段建立聚簇索引。但是,你也可以對其它類型的字段建立聚簇索引,如字符型,數值型和日期時間型字段。

從建立了聚簇索引的表中取出數據要比建立了非聚簇索引的錶快。當你需要取出一定範圍內的數據時,用聚簇索引也比用非聚簇索引好。例如,假設你用一個表來記錄訪問者在你網點上的活動。如果你想取出在一定時間段內的登錄信息,你應該對這個表的DATETIME型字段建立聚簇索引。

對聚簇索引的主要限制是每個表只能建立一個聚簇索引。但是,一個表可以有不止一個非聚簇索引。實際上,對每個表你最多可以建立249個非聚簇索引。你也可以對一個表同時建立聚簇索引和非聚簇索引。

假如你不僅想根據日期,而且想根據用戶名從你的網點活動日誌中取數據。在這種情況下,同時建立一個聚簇索引和非聚簇索引是有效的。你可以對日期時間字段建立聚簇索引,對用戶名字段建立非聚簇索引。如果你發現你需要更多的索引方式,你可以增加更多的非聚簇索引。

非聚簇索引需要大量的硬盤空間和內存。另外,雖然非聚簇索引可以提高從表中 取數據的速度,它也會降低向表中插入和更新數據的速度。每當你改變了一個建立了非聚簇索引的表中的數據時,必須同時更新索引。因此你對一個表建立非聚簇索引時要慎重考慮。如果你預計一個表需要頻繁地更新數據,那麼不要對它建立太多非聚簇索引。另外,如果硬盤和內存空間有限,也應該限制使用非聚簇索引的數量。


索引屬性
這兩種類型的索引都有兩個重要屬性:你可以用兩者中任一種類型同時對多個字段建立索引(複合索引);兩種類型的索引都可以指定爲唯一索引。

你可以對多個字段建立一個複合索引,甚至是複合的聚簇索引。假如有一個表記錄了你的網點訪問者的姓和名字。如果你希望根據完整姓名從表中取數據,你需要建立一個同時對姓字段和名字字段進行的索引。這和分別對兩個字段建立單獨的索引是不同的。當你希望同時對不止一個字段進行查詢時,你應該建立一個對多個字段的索引。如果你希望對各個字段進行分別查詢,你應該對各字段建立獨立的索引。

兩種類型的索引都可以被指定爲唯一索引。如果對一個字段建立了唯一索引,你將不能向這個字段輸入重複的值。一個標識字段會自動成爲唯一值字段,但你也可以對其它類型的字段建立唯一索引。假設你用一個表來保存你的網點的用戶密碼,你當然不希望兩個用戶有相同的密碼。通過強制一個字段成爲唯一值字段,你可以防止這種情況的發生。 

詳解聚簇索引
 

    一、聚族索引的構造

聚簇索引並不是一種單獨的索引類型,而是一種數據存儲方式。具體的細節依賴於其實現方式,但InnoDB的聚族索引實際上在同一個結構中保存了B-Tree索引和數據行。當表有聚族索引時,它的數據行存放在索引的葉子頁中。術語“聚族”表示數據行和相鄰的鍵值緊湊的存儲在一起。因爲無法同時把數據行放在兩個不同的地方,所以一個表只能有一個聚族索引。

因爲是存儲引擎負責實現索引,因此不是所有的存儲引擎都支持聚族索引。這裏我們主要關注InnoDB,但是這裏討論的原理對於任何支持聚族索引的存儲引擎都是適用的。

下面展示了聚族索引中的記錄是如何存放的。注意到,葉子頁包含了行的全部數據,但是節點頁只包含了索引列。

 在InnoDB中通過主鍵聚集數據,這也就是說上圖中“被索引的列”就是主鍵列。如果沒有定義主鍵,InnoDB會選擇一個唯一的非空索引代替。如果沒有這樣的索引,InnoDB會隱式定義一個主鍵來作爲聚族索引。InnoDB只聚集在同一個頁面中的記錄。包含相鄰鍵的頁面可能會相距甚遠。

聚族主鍵可能對性能有幫助,但也可能導致嚴重的性能問題。所以需要仔細的考慮聚族索引,尤其是將表的引擎從InnoDB改成其他引擎的時候。

二、聚族索引的優點

  • 可以把相關數據保存在一起。例如實現電子郵件時,可以根據用戶ID來聚集數據,這樣只需要從磁盤讀取少數的數據頁就能獲取某個用戶的全部郵件。如果沒有使用聚族索引,則每封郵件都可能導致一次磁盤I/O;
  • 數據訪問更快。聚族索引將索引和數據保存在同一個B-Tree中,因此從聚族索引中獲取數據通常比在非聚族索引中查找更快。
  • 使用覆蓋索引掃描的查詢可以直接使用節點中的主鍵值。

三、聚族索引的缺點

  • 聚簇數據最大限度的提高了I/O密集型應用的性能,但如果數據全部都放在內存中,則訪問的順序就沒有那麼重要了,聚簇索引也就沒有那麼優勢了;
  • 插入速度嚴重依賴於插入順序。按照主鍵的順序插入是加載數據到InnoDB表中速度最快的方式。但如果不是按照主鍵順序加載數據,那麼在加載完成後最好使用OPTIMIZE TABLE命令重新組織一下表。
  • 更新聚簇索引列的代價很高,因爲會強制InnoDB將每個被更新的行移動到新的位置。
  • 基於聚簇索引的表在插入新行,或者主鍵被更新導致需要移動行的時候,可能面臨“頁分裂”的問題。當行的主鍵值要求必須將這一行插入到某個已滿的頁中時,存儲引擎會將該頁分裂成兩個頁面來容納該行,這就是一次分裂操作。頁分裂會導致表佔用更多的磁盤空間。
  • 聚簇索引可能導致全表掃描變慢,尤其是行比較稀疏,或者由於頁分裂導致數據存儲不連續的時候。
  • 二級索引(非聚簇索引)可能比想象的要更大,因爲在二級索引的葉子節點包含了引用行的主鍵列。
  • 二級索引訪問需要兩次索引查找,而不是一次。

備註: 有關二級索引需要兩次索引查找的問題?答案在於二級索引中保存的“行指針”的實質。要記住,二級索引葉子節點保存的不是指向行的物理位置的指針,而是行的主鍵值。這意味着通過二級索引查找行,存儲引擎需要找到二級索引的葉子節點獲得對應的主鍵值,然後根據這個值去聚簇索引中查找到對應的行。這裏做了重複的工作:兩次B-Tree查找而不是一次。對於InnoDB,自適應哈希索引能夠減少這樣的重複工作。

四、InnoDB和MyISAM的數據分佈對比

聚簇索引和非聚簇索引的數據分佈有區別,以及對應的主鍵索引和二級索引的數據分佈也有區別。

1、MyISAM的主鍵索引和二級索引

MyISAM的數據分佈非常簡單,MyISAM按照數據插入的順序存儲在磁盤上。在行的旁邊顯示了行號,從0開始遞增。因爲行是定長的,所以MyISAM可以從表的開頭跳過所需的字節找到需要的行。這種分佈方式很容易創建索引。並且,MyISAM中主鍵索引和其他索引在結構上沒有什麼不同。主鍵索引就是一個名爲primary的唯一非空索引。如下圖:

1、MyISAM數據行分佈

 2、MyISAM的主鍵分佈

 3、MyISAM上的其他索引分佈

2、InnoDB的主鍵索引和二級索引

InnoDB的數據分佈,因爲InnoDB支持聚簇索引,索引使用非常不同的方式存儲這樣的數據,如下圖:

 仔細查看,會注意到該圖顯示了整個表,而不是隻有索引。因爲在InnoDB中,聚簇索引“就是”表,所以不像MyISAM那樣需要獨立的行存儲。聚簇索引的每個葉子節點都包含了主鍵值、事務ID、用於事務和MVCC的回滾指針以及所有的剩餘列。如果主鍵是一個列前綴索引,InnoDB也會包含完整的主鍵列和剩下的其他列。

還有一點和MyISAM的不同是,InnoDB的二級索引和聚簇索引很不相同。InnoDB二級索引的葉子節點中存儲的不是“行指針”,而是主鍵值,並以此作爲指向行的“指針”。這樣的策略減少了當出現航移動或者數據頁分裂時二級索引的維護工作。使用主鍵值當作指針會讓二級索引佔用更多的空間,換來的好處是,InnoDB在移動行時無需更新二級索引中的這個“指針”。下圖就是InnoDB的二級索引:

 3、MyISAM和InnoDB的對比

 五、在InnoDB表中按主鍵順序插入行

如果正在使用InnoDB表並且沒有什麼數據需要聚集,那麼可以定義一個代理鍵作爲主鍵,這種主鍵的數據應該和應用無關,最簡單的方法是使用auto_increment自增列。這樣可以保證數據行是按照順序寫入,對於根據主鍵做關聯操作的性能也會更好。

最好避免隨機的聚簇索引,特別對於I/O密集型的應用。例如,從性能的角度考慮,使用UUID作爲聚簇索引會很糟糕:它使得聚簇索引的插入變得完全隨機,這是最壞的情況,使得數據沒有任何聚集特性。通過測試,向UUID主鍵插入行不僅花費的時間更長,而且索引佔用的空間也更大。這一方面是由於主鍵字段更長,另一方面毫無疑問是由於頁分裂和碎片導致的。

這是由於當主鍵的值是順序的,則InnoDB把每一條記錄都存儲在上一條記錄的後面。當達到頁的最大填充因子時(InnoDB默認的最大填充因子是頁大小的15/16,留出的部分空間用於以後修改),下一條記錄就會寫入新的頁中。一旦數據按照這樣順序的方式加載,主鍵頁就會近似於被順序的記錄填滿,這也是所期望的結果。

而當採用UUID的聚簇索引的表插入數據,因爲新行的主鍵值不一定比之前的插入值大,所以InnoDB無法簡單的總是把新行插入到索引的最後,而是需要爲新的行尋找合適的位置----通常是已有數據的中間位置----並且分配空間。這會增加很多額外的工作,並導致數據分佈不夠優化。下面是總結的一些缺點:

  • 寫入目標頁可能已經刷到磁盤上並從緩存中移除,或者是還沒有被加載到緩存中,InnoDB在插入之前不得不先找到並從磁盤讀取目標頁到內存中,這將導致大量的隨機I/O;
  • 因爲寫入是亂序的,InnoDB不得不頻繁的做頁分裂操作,以便爲新的行分配空間。頁分裂會導致移動大量數據,一次插入最少需要修改三個頁而不是一個頁。
  • 由於頻繁的頁分裂,頁會變得稀疏並被不規則的填充,所以最終數據會有碎片。
  • 把這些隨機值載入到聚簇索引以後,需要做一次optimize table來重建表並優化頁的填充。

注意:順序主鍵也有缺點:對於高併發工作負載,在InnoDB中按主鍵順序插入可能會造成明顯的爭用。主鍵的上界會成爲“熱點”。因爲所有的插入都發生在這裏,所以併發插入可能導致間隙鎖競爭。另一個熱點可能是auto_increment鎖機制;如果遇到這個問題,則可能需要考慮重新設計表或者應用,或者更改innodb_autonc_lock_mode配置

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章