MySQL總結之索引

索引是存儲引擎用於快速找到記錄的一種數據結構。

索引對於良好的性能非常關鍵。尤其是當表中的數據量越來越大時,索引對性能的影響愈發重要。在數據量較小且負載較低時,不恰當的索引對性能的影響可能還不明顯,但當數據量逐漸增大時,性能則會急劇下降。

在 MySQL 中,存儲引擎用類似的方法使用索引,其先在索引中找到對應值,然後根據匹配的索引記錄找到對應的數據行。

索引可以包含一個或多個列的值。如果索引包含多個列,那麼列的順序也十分重要,因爲MySQL只能高效地使用索引的最左前綴列。

索引的類型

在MySQL中,索引是在存儲引擎層而不是服務器層實現的。

B-Tree 索引

當人們談論索引的時候,如果沒有特別指明類型,那多半說的是B-Tree索引,它使用B-Tree數據結構來存儲數據。

我們使用術語“B-Tree”,是因爲MySQL在CREATE TABLE和其他語句中也適用該關鍵字。不過,底層的存儲引擎也可能使用不同的存儲結構,例如,NDB 集羣存儲引擎內部實際上使用了T-Tree 結構存儲這中索引,即使其名字是BTREE;InnoDB 則使用的是B+Tree。

存儲引擎以不同的方式使用B-Tree索引,性能也各有不同,各有優劣。例如,MyISAM使用前綴壓縮技術使得索引更小,但InnoDB則按照原數據格式進行存儲。再如MyISAM索引通過數據的物理位置引用被索引的行,而InnoDB則根據主鍵引用被索引的行。

B-Tree 通常意味着所有的值都是按順序存儲的,並且每一個葉子頁到根的距離相同。下圖展示了B-Tree索引的抽象表示,大致反映了InnoDB索引是如何工作的。

建立在B-Tree結構(從技術上來說是B+Tree)上的索引

B-Tree 索引能夠加快訪問數據的速度,因爲存儲引擎不再需要進行全表掃描來獲取需要的數據,取而代之的是從索引的根結點開始進行搜索。根結點的槽中存放了指向子節點的指針,存儲引擎根據這些指針向下層查找。通過比較節點頁的值和要查找的值可以找到合適的指針進入下層子節點,這些指針實際上定義了子節點頁中值的上限和下限。最終存儲引擎要麼是找到對應的值,要麼該記錄不存在。

葉子節點比較特別,它們的指針指向的是被索引的數據,而不是其他的節點頁(不同存儲引擎的“指針”類型不同)。上圖中近繪製了一個節點和其對應的葉子節點,其實在根節點和葉子節點之間可能有很多節點層。樹的深度和表的大小直接相關。

B-Tree對索引列是順序存組織存儲的,所以很適合查找範圍數據。

可以使用B-Tree索引的查詢類型。B-Tree 索引適用於全健值、健值範圍或健前綴查找。其中健前綴查找只適用於最左前綴查找。B-Tree 索引對如下類型的查詢最有效:

  • 全值匹配
  • 匹配最左前綴
  • 匹配列前綴
  • 匹配範圍值
  • 精確匹配某一列並範圍匹配另一列
  • 只訪問索引的查詢

B-Tree 索引的限制:

  • 如果不是按照索引的最左列開始查找,則無法使用索引。
  • 不能跳過索引中的列。
  • 如果查詢中有某個列的範圍查詢,則其右邊所有列都無法使用索引優化查找。

所以,索引列的順序是很重要的:上面的限制都和索引列的順序有關。

哈希索引

哈希索引基於哈希索引實現,只有精確匹配所有所有列的查詢纔有效。 對於每一行數據,存儲引擎都會對所有的索引列精算一個哈希碼,哈希碼是一個較小的值,並且不同健值的行計算出來的哈希碼也不一樣。哈希索引將所有的哈希碼存儲在索引中,同時在哈希表中保存指向每個數據行的指針。

哈希索引的限制:

  • 哈希索引只包含哈希值和行指針,而不存儲字段值,所以不能使用索引中的值來避免讀取行。
  • 哈希索引數據並不是按照索引值順序存儲的,所以也就無法用於排序。
  • 哈希索引也不支持部分索引列匹配查找,因爲哈希索引始終是使用索引列的全部內容來計算哈希值的。
  • 哈希索引只支持等值比較查詢。不支持任務範圍查詢。
  • 訪問哈希索引的數據非常快,除非有很多哈希衝突(不同索引列值卻有相同的哈希值)。當出現哈希衝突的時候,存儲引擎必須遍歷鏈表中所有的行指針,逐行進行比較,直到找到所有符合條件的行。
  • 如果哈希衝突很多的話,一些索引維護代價也會很高。

因爲這些限制哈希索引只適用於某些特定的場合。

InnoDB引擎有一個特殊的功能叫做“自適應哈希索引”。當InnoDB注意到某些索引值被使用得非常頻繁時,它會在內存中基於B-Tree索引之上再創建一個哈希索引,這樣就讓B-Tree索引也具有哈希索引的一些優點,比如快速的哈希查找。這是一個完全自動的、內部的,用戶無法控制或者配置。

全文索引

全文索引是一種特殊類型的索引,它查找的是文本中的關鍵詞,而不是直接比較索引中的值。

索引的優點

索引可以讓服務器快速地定位到表的指定位置。但這並不是索引唯一作用,根據創建索引的數據結構不同,索引也有一些其他的附加作用。

最常見的B-Tree 索引,按照順序存儲數據,所以MySQL 可以用來做ORDER BY 和 GROUP BY 操作。因爲數據是有序的,索引B-Tree也就會將相關的列值都存儲在一起。最後,因爲索引中存儲了實際的列值,所以某些查詢只使用索引就能夠完成全部查詢。總結下來索引有如下三個優點:

  1. 索引大大減少了服務器需要掃描的數據量。
  2. 索引可以幫助服務器避免排序和臨時表。
  3. 索引可以將所及I/O變爲順序I/O。

評價一個索引是否適合某個查詢的“三星系統”:索引將相關的記錄放到一起則獲得“一星”;如果索引中的數據順序和查找中的排列順序一致則獲得“二星”;如果索引中的列包含了查詢中需要的全部列則獲得“三星”。

高性能的索引策略

正確的創建和使用索引是實現高性能查詢的基礎。

獨立的列

如果查詢中的列不是獨立的,則MySQL就不會使用索引。“獨立的列”是指索引列不能是表達式的一部分,也不能是函數的參數。

例如,下面這個查詢無法使用actor_id列的索引:

mysql>select * from sakila.action_id + 1 = 5;

憑肉眼很容易看出where中的表達式其實等價於actor_id = 4,但是MySQL無法自動解析這個方程式。這完全是用戶行爲。我們應該養成簡化where條件的習慣,始終保持將索引列單獨放在比較符號的一側。

前綴索引和索引選擇性

有時候需要索引很長的字符列,這會讓索引變得大且慢。一個策略是前面提到過的模擬哈希索引。但有時候這樣做還不夠,還可以做些什麼呢?

通常可以索引開始的部分字符,這樣可以大大節約索引空間,從而提高索引效率。但這樣也會降低索引的選擇性。索引的選擇性是指,不重複的索引值(也稱爲基數)和數據表的記錄總數(#T)的比值,範圍從1/#T到1之間。索引的選擇性越高則查詢效率越高,因爲選擇性高的索引可以讓MySQL在查找時過濾掉更多的行。唯一索引的選擇性是1,這是最好的索引選擇性,性能也是最好的。

一般情況下某個列的前綴選擇性也是足夠高的,足以滿足查詢性能。對於BLOG、TEXT或者很長的VARCHAR類型的列,必須使用前綴索引,因爲MySQL不允許索引這些的完整長度。

訣竅在於選擇足夠長的前綴以保證較高的選擇性,同時又不能太長(以便節約空間)。前綴應該足夠長,以使得前綴索引的選擇性接近於索引整個列。

選擇合適的索引順序

我們遇到的最容易引起困惑的問題就是索引列的順序。正確的索引順序依賴於使用該索引的查詢,並且同時需要考慮如何更地滿足排序和分組的需要。

在一個多列B-Tree索引中,索引列的順序意味着索引首先按照最左列進行排序,其次是第二列,等等。所以,索引可以按照生序或者降序進行掃描,以滿足精確符合列順序的ORDER BY、GROUP BY和DISTINCK等子句的查詢需求。

當不需要考慮排序和分組時,將選擇性最高的列放在前面通常是很好的。這時候索引的作用只是用於優化WHERE條件查找。

聚簇索引

聚簇索引並不是一種單獨的索引類型,而是一種數據存儲方式。 請注意,這句話十分重要,這句話的意思翻譯一下就是說,聚簇索引是一種物理存儲結構而不是建表時候指定的一種索引類型。

InnoDB會以聚簇索引的形式來存儲實際的數據,它是其他二級索引的基礎。

InnoDB建立聚簇索引的方式:

  1. InnoDB對主鍵建立聚簇索引。
  2. 如果你不指定主鍵,InnoDB會用一個具有唯一且非空值的索引來代替。
  3. 如果不存在這樣的索引,InnoDB會定義一個隱藏的主鍵,然後對其建立聚簇索引。

InnoDB的聚簇索引實際上是在同一個結構種保存了B-Tree索引和數據行。

聚簇索引的優點:

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

聚簇索引的缺點:

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

最後一點可能讓人有些疑惑,爲什麼二級索引需要兩次索引查找?答案在於二級索引中保存的“行指針”的實質。要記住,二級索引葉子節點保存的不是指向行的物理位置的指針,而是行的主鍵值。

這意味着通過二級索引查找行,存儲引擎需要找到二級索引的葉子節點獲得對應的主鍵值,然後根據這個值去聚簇索引中查找到對應的行。對於InnoDB,自適應哈希索引能夠減少這樣的重複工作。

覆蓋索引

如果一個索引包含(或者說是覆蓋)所有需要查詢的字段值,我們就稱之爲“覆蓋索引”。

覆蓋索引是非常有用的工具,能夠極大地提高性能。考慮一下如果查詢需要掃描索引而無需回表,會帶來多少好處。

總結

總的來說,編寫查詢語句時應該儘可能選擇合適的索引以避免單行查找、儘可能地使用數據原生順序從而避免額外的排序操作,並儘可能使用索引覆蓋查詢。

我對於索引的認知是從上次在一個有1000多萬行記錄的表中根據一個沒有索引的字段進行條件查詢開始的,天知道那是怎樣的一種痛苦…

有一些經驗再來學這些基礎的知識,感覺十分不一樣。

歡迎關注我的公衆號:荒古傳說

本文作者: 荒古
本文鏈接: https://haxianhe.com/2019/08/09/MySQL學習筆記之索引/
版權聲明: 本博客所有文章除特別聲明外,均採用 CC BY-NC-SA 3.0 許可協議。轉載請註明出處!

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