MySQL索引詳解及優化策略

1. 索引介紹

索引是存儲引擎用於快速查找記錄的一種數據結構。索引優化是對查詢性能優化最有效的手段。
索引可以包含一個或多個列的值。如果索引包含多個列,那麼列的順序也十分重要,因爲MySQL只能高效地使用索引的最左前綴列

1.1 索引優點

索引可以讓服務器快速定位到表的指定位置:最常見的B-Tree索引,按照順序存儲數據,所以MySQL可以用來做ORDER BY 和 GROUP BY操作.
總結下來,索引有如下三個優點:

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

1.2 適合建立索引的字段

  1. 經常搜索
  2. 經常排序
  3. 經常跟在Where語句後面
  4. 數據類型小
  5. 簡單數據類型
  6. 列中儘量避免null

2. 索引類型

2.1 B-Tree索引

使用B-Tree數據結構來存儲數據,大多MySQL引擎都支持該索引。B-Tree索引可以加快訪問數據的速度,因爲B-Tree對索引列順序組織存儲,範圍查找快.

  1. 主鍵和唯一性約束字段的B樹索引,效率幾乎和海量數據沒有關係。
  2. 鍵值重複率低的字段比較適合使用B樹索引。
  • 唯一索引
    索引列的所有值都只能出現一次,即必須唯一,值可以爲空,一對一關係。
    普通索引(一對多關係)是允許數據重複的,如果確定了某列數據不會重複,則可創建唯一索引,唯一索引有兩個好處,索引更有效:插入新數據,如果重複,MySQL拒絕插入。
  • 主鍵索引
    主鍵是一種唯一性索引,但它必須指定爲PRIMARY KEY,每個表只能有一個主鍵
    主鍵本身默認創建索引

2.1.1 可以使用B-Tree索引的查詢類型

  1. 全值匹配:全值匹配指的是和索引中的所有列進行匹配。
  2. 匹配最左前綴:即只使用索引的第一列。
  3. 匹配列前綴:也可以只匹配某一列的值的開頭部分。
  4. 匹配範圍值
  5. 精確匹配某一列並範圍匹配另外一列
  6. 只訪問索引的查詢:覆蓋索引

2.1.2 索引順序查詢

因爲索引樹中的節點是有序的,所以除了按值查找之外,索引還可以用於查詢中的ORDER BY操作(按順序查找)。
一般來說,如果B-Tree可以按照某種方式查找到值,那麼也可以按照這種方式用於排序。

2.1.3 B-Tree索引的限制

  1. 如果不是按索引的最左列開始查找,則無法使用索引。
  2. 不能跳過索引中的列。
  3. 如果查詢中有某個列的範圍查詢,則最右邊的所有列都無法使用索引優化查詢。如果範圍查詢列值的數量有限,那麼可以通過使用多個等於條件來代替範圍查找。

2.2 Hash索引(Memory引擎支持)

哈希索引基本哈希表實現,只有精確匹配索引所有列的查詢纔有效。結構十分緊湊,查詢速度非常快。
對於每一行數據,存儲引擎都會對所有的索引列計算一個哈希碼,哈希碼值較小。哈希索引將所有的哈希碼存儲在索引中,同時在哈希表中保存指向每個數據行的指針。MySQL中只有Memory引擎顯示支持哈希索引。哈希表只能在內存實現,沒法在磁盤實現.
索引列會被存儲在匹配到的hash bucket裏面的表裏,這個表裏會有實際的數據行指針,再根據實際的數據行指針查找對應的數據行。
hash索引適用於等值(=)查詢
在這裏插入圖片描述

2.2.1 Hash索引流程

概括來說,要查找一行數據或者處理一個where子句:

  1. 根據where條件裏面的參數生成合適的哈希函數
  2. 索引列進行匹配,匹配到對應hash bucket,找到對應hash bucket意味着也找到了對應的數據行指針(row pointer)
  3. 讀取數據
    哈希索引比起B樹索引簡單,因爲它不需要遍歷B樹,所以訪問速度會更快
    在這裏插入圖片描述

2.2.2 Hash索引的缺點

  1. 因爲Hash索引比較的是經過Hash計算的值,所以只能進行等式比較,不能用於範圍查詢
  2. 由於哈希值是按照順序排列的,但是哈希值映射的真正數據在哈希表中就不一定按照順序排列,所以無法利用Hash索引來加速任何排序操作
  3. 不能用部分索引鍵來搜索,因爲組合索引在計算哈希值的時候是一起計算的。
  4. 當哈希值大量重複且數據量非常大時,其檢索效率並沒有B-tree索引高。

2.3 空間索引(R-Tree)

MyIsam表支持空間索引,Mysql本身對GIS的支持並不完善
開源關係數據庫中對GIS的解決方案做的比較好的是PostgreSQL的PostGIS

2.4 全文索引(MyISAM引擎支持)

全文索引是一種特殊的索引,它查找的是文本中的關鍵詞而不是直接比較索引中的值。
全文索引更加類似於搜索引擎做的事情,而不是簡單的WHERE匹配。在同一列上同時創建全文索引和基於值的B-Tree索引不會有衝突。
全文索引主要用於詞彙的快速搜索,比較適合對列中的字段進行模糊查詢(如SQL語句中包含%)或者語言類的查詢,其缺點是佔用空間太大(會創建很多中間表)

應用場景
文本字段上的普通索引只能加快對出現在字段內最前面的字符串進行的檢索操作,如果字段裏存放的是由幾個或者多個單詞構成的大段文字,普通索引就不行了,這種場合用全文索引比較合適
效率
查詢效率:唯一索引>自增主鍵>主鍵
插入效率:主鍵>自增主鍵>唯一索引
創建全文索引

  • 創建article表
CREATE TABLE article (
id INT UNSIGNED AUTO_INCREMENT NOT NULL PRIMARY KEY,
title VARCHAR(200),
content TEXT,
FULLTEXT (title, content) —在title和content列上創建全文索引
);

給現有的article表的title和content字段創建全文索引
索引名稱爲fulltext_article

ALTER TABLE article
ADD FULLTEXT INDEX fulltext_article (title, content)

使用全文索引

SELECT * FROM article WHERE MATCH(title, content) AGAINST(‘查詢字符串’)

不使用全文索引

SELECT * FROM article WHERE content LIKE%查詢字符串%

強烈注意:MySQL自帶的全文索引只能用於數據庫引擎爲MyISAM的數據表,如果是其他數據引擎,則全文索引不會生效。此外,MySQL自帶的全文索引只能對英文進行全文檢索,目前無法對中文進行全文檢索。如果需要對包含中文在內的文本數據進行全文檢索,我們需要採用Sphinx(斯芬克斯)/Core

  • 備註1:目前,使用MySQL自帶的全文索引時,如果查詢字符串的長度過短將無法得到期望的搜索結果。MySQL全文索引所能找到的詞的默認最小長度爲4個字符。另外,如果查詢的字符串包含停止詞,那麼該停止詞將會被忽略。
  • 備註2:如果可能,請儘量先創建表並插入所有數據後再創建全文索引,而不要在創建表時就直接創建全文索引,因爲前者比後者的全文索引效率要高。

3. 高性能的索引策略

3.1 獨立的列

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

3.2 前綴索引和索引選擇性

有時候需要索引很長的字符列,這會讓索引變得很大且慢。通常可以索引開始部分的字符,這樣可以大大節約索引空間,從而提高索引效率。但這樣會降低索引的選擇性。

  • a. 索引的選擇性是指,不重複的索引值(也稱爲基數)和數據表的記錄總數(#T)的比值.
    範圍從1/#T到1之間。索引的選擇性越高則查詢效率越高,因爲選擇性高的索引可以讓MySQL在查找時過濾掉更多的行。
    唯一索引的選擇性是1,這是最好的索引選擇性,性能也是最好的。

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

  • c. 訣竅在於要選擇足夠長的前綴以保證較高的選擇性,同時又不能太長(以便節省空間)。
    計算合適的前綴長度的一個方法是計算完整列的選擇性,並使前綴的選擇性接近於完整列的選擇性。

下面是如何計算完整列的選擇性

SELECT COUNT(DISTINCT city)/COUNT(*) FROM sakila.city_demo;        
查詢結構值爲:0.031
通常來說,這個例子中如果前綴的選擇性能夠接近於0.031,基本上就可以用了。
可以在一個查詢中針對不同前綴長度進行計算,這對於大表非常有用。

下面給出瞭如何在同一個查詢中計算不同前綴長度的選擇性:

SELECT COUNT(DISTINCT LEFT(city,3)) AS sel3,
COUNT(DISTINCT LEFT(city,4)) AS sel4,COUNT(DISTINCT LEFT(city,5)) AS sel5,
COUNT(DISTINCT LEFT(city,6)) AS sel6,COUNT(DISTINCT LEFT(city,7)) AS sel7 
FROM sakila.city_demo;
查詢結果值,按順序爲:0.0238 ,  0.0293, 0.0305,0.0309,0.0310
查詢顯示當前前綴長度到達7的時候,再增加前綴長度,選擇性提升的幅度已經很小了。

  • d. 只看平均選擇性是不夠的,也有例外的情況,需要考慮最壞情況下的選擇性。
  • e. 前綴索引是一種能使索引更小、更快的有效辦法。但另一方面也有其缺點:MySQL無法使用前綴索引做ORDER BY和GROUP BY , 也無法使用前綴索引做覆蓋掃描。
  • f. 有時候後綴索引也有用途(例如,找到某個域名的所有電子郵件地址)。MySQL原生並不支持反向索引,但是可以把字符串反轉後存儲,並基於此建立前綴索引。可以通過觸發器來維護這種索引。

3.3 多列索引

在MySQL或更新的版本中,會使用”索引合併”策略,查詢能同時使用兩個單列索引進行掃描,並將結果進行合併。這種算法有三個變種:

1.OR條件的聯合(union),
2.AND條件的相交
3.組合前兩種情況的聯合及相交

索引合併策略有時候是一種優化的結果,但實際上更多的時候說明表上的索引建的很糟糕:

  • 當出現服務器對多個索引做相交操作時(通常多個AND條件),通常意味着需要一個包含所有相關列的多列索引,而不是多個獨立的單列索引。
  • 當服務器需要對多個索引做聯合操作時(通常由多個OR條件),通常需要耗費大量CPU和內存資源在算法的緩存、排序和合並操作上。
    特別是當其中有些索引的選擇性不高,需要合併掃描返回的大量數據時。
  • 優化器不會把這些計算到”查詢成本”(cost)中,優化器值關係隨機頁面讀取。
    這樣不僅消耗更多的CPU和內存資源,還會影響查詢的併發性。
  • 如果在EXPLAIN中看到有索引合併,應該好好檢查一下查詢和表的結構,看是不是已經最優的。也可以哦太難過參數optimizer_switch來關閉索引合併功能。也可以使用IGNORE INDEX提示讓優化器忽略掉某些索引。

3.4 選擇合適的索引列順序

正確的索引順序依賴於使用該索引的查詢,並同時滿足需要考慮如何更好地滿足排序和分組的需要。

  • a. 在一個多列的B-Tree索引中,索引列的順序意味着索引首先按照最左列進行排序,其次是第二列,等等。所以,索引可以按照升序或者降序進行全表掃描,以滿足符合列順序的ORDER BY,GROUP BY和DISTINCT等字句的查詢需求。
  • b. 當不需要考慮排序和分組時,將選擇性最高的列放在前面通常是很好的。這時候索引的作用只是用於優化WHERE條件查詢。
  • c. 更具運行頻率最高的查詢來調整索引的順序,讓這種情況下索引的選擇性最高。
  • d. 如果是從諸如pt-query-digest這樣的工具的報告中提起”最差”查詢,那麼再按上面辦法選定索引順序往往是非常高效的。

如果沒有類似的具體查詢來運行,那麼最好還是按經驗法則來做,因爲全局法則考慮的是全局基數和選擇性,而不是某個具體查詢。

SELECT COUNT(DISTINCT staff_id)/COUNT(*) AS staff_id_selectivity,
COUNT(DISTINCT customer_id)/COUNT(*) AS customer_id_selectivity, 
count(*) from payment
查詢結果值爲:
staff_id_selectivity  0.0001,customer_id_selectivity  0.0373count(*)  16049 
customer_id 的選擇性更高,所以答案是將其作爲索引列的第一列

3.5 聚簇索引

InnoDB的聚簇索引實際上在同一個結構中保存了B-Tree索引和數據行。它的數據實際上存儲在索引的葉子頁中。” 聚簇”表示把數據行和相鄰的鍵值緊湊地存儲在一起。因爲無法同時把數據行存放在兩個不同的地方,所以一個表只能有一個聚簇索引(即主鍵索引)。

非聚簇索引

  1. 非聚集索引制定了表中記錄的邏輯順序
  2. 非聚集索引強調的是邏輯分類。可以說是定義了一套存儲規則,而需要有一塊控件來維護這個規則,這個被稱之爲索引表。

但是記錄的物理和索引不一定一致,兩種索引都採用B+樹結構,非聚集索引的葉子層並不和實際數據頁相重疊,而採用葉子層包含一個指向表中的記錄在數據頁中的指針方式。非聚集索引層次多,不會造成數據重排。
非聚集索引葉節點仍然是索引節點,只是有一個指針指向對應的數據塊,此如果使用非聚集索引查詢,而查詢列中包含了其他該索引沒有覆蓋的列,那麼他還要進行第二次的查詢,查詢節點上對應的數據行的數據。

聚簇索引與非聚簇索引區別
聚集索引和非聚集索引的根本區別是表記錄的排列順序和與索引的排列順序是否一致。

InnoDB將通過主鍵聚集數據。
如果沒有定義主鍵,InnoDB會選擇一個唯一的非空索引代替。如果沒有這樣的索引,InnoDB會隱式定義一個主鍵作爲聚簇索引。InnoDB只聚集在同一個頁面中的記錄。包含相鄰鍵值的頁面可能會相距甚遠。

聚簇索引的優點:

  • a. 可以把相關的數據保存在一起。
  • b. 數據訪問更快。
  • c. 使用覆蓋索引掃描的查詢可以直接使用葉節點中的主鍵值。

聚簇索引的缺點:

  • a. 更新聚簇索引列的代價很高,因爲會強制將每個被更新的行移動到新的位置。
  • b. 可能會導致頁分裂。
  • c. 導致全表掃描變慢,尤其是行比較稀疏。
  • e. 二級索引(非聚簇索引)可能比想象的要更大,因爲在二級索引的葉子節點包含了引用行的主鍵列。
  • f. 二級索引訪問需要兩次索引查詢,而不是一次。
    這是因爲二級索引葉子節點保存的不是指向行的物理位置的指針,而是行的主鍵值。這意味着通過二級索引查找行,存儲引擎需要找到二級索引的葉子節點獲得對應的主鍵值,然後根據這個值去聚簇索引中查找到對應的行。這裏做了重複工作:兩次B-Tree查找而不是一次。

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

使用InnoDB時應該儘可能地按主鍵順序插入數據,並且儘可能地使用單調增加的聚簇鍵的值插入新行。
這樣可以順序地寫入數據,減少隨機IO,減少碎片和減少分頁。
但對於高併發工作負載,按主鍵順序插入可能造成明顯的爭用。

3.6 覆蓋索引

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

覆蓋索引的好處

  1. 索引條目通常遠小於數據行大小,所以如果只需要讀取索引,那麼MySQL就會極大地減少數據訪問量。
  2. 因爲索引是按照值順序存儲的(至少在單個頁內如此),所以對於IO密集型的範圍查詢會比隨機從磁盤讀取每一行數據的IO要少得多。
  3. 由於InnoDB的聚簇索引,覆蓋索引對InnoDB表特別有用。 InnoDB的二級索引在葉子節點中保存了行的主鍵值,所以如果二級主鍵能夠覆蓋查詢,則可以避免對主鍵索引的二次查詢。

不是所有類型的索引都可以作爲覆蓋索引
覆蓋索引必須要存儲索引列的值,而哈希索引、空間索引和全文索引等都不存儲索引列的值,所以MySQL只能使用B-Tree索引做覆蓋索引。不是所有的索引都支持覆蓋索引。

當發起一個索引覆蓋的查詢時,在EXPLAIN的Extra列可以看到”Using index”的信息。

MySQL不能在索引中執行LIKE操作
因爲該操作可能轉換爲簡單的比較操作,但是如果是通配符開頭的LIKE查詢,存儲引擎就無法比較匹配。MySQL服務器只能提取數據行的值而不是索引值來作比較。

延遲關聯
延遲對列的訪問。在查詢第一階段MySQL可以做覆蓋索引,在FROM子句的子查詢中使用索引。

3.7 使用索引掃描來做排序

MySQL有兩種方式可以生成有序的結果

通過排序操作;或者按索引順序掃描;如果EXPLAIN出來的type列的值爲”index”,則說明MySQL使用了索引來做排序(不要和Extra列的”Using index”搞混淆了)。

  • a. 索引本身是很快的,因爲只需要從一條索引記錄移動到緊接着的下一條記錄。如果索引不能覆蓋查詢所需的全部列,那就不得不每掃描一條索引記錄就回表查詢一次對應的行。這基本上都是隨機IO,因此按索引順序讀取數據的速度通常要比順序地全表掃描慢,尤其是在IO密集型的工作負載時。
  • b. 只有當索引的列順序和ORDER BY子句順序完全一致,並且所有列的順序方向(倒序或正序)都一樣時MySQL才能夠使用索引來對結果做排序。如果查詢需要關聯多張表,則只有ORDER BY子句引用的字段全部爲第一個表時,才能使用索引做排序。
  • c. 有一種情況下ORDER BY子句可以不滿足索引的最左前綴的要求,就是前導列爲常量的時候。

3.8 壓縮(前綴索引)

MyISAM使用前綴索引壓縮來減少索引的大小,從而讓更多的索引可以放入內存中,這在某些情況下能極大地提高性能。默認值壓縮字符串,到哪通過參數設置可以對整數壓縮。

3.9 冗餘和重複索引

MySQL允許在相同列上創建多個索引,無論是有意還是無意的。MySQL需要單獨維護重複的索引,並且優化器在優化查詢的時候也需要逐個地進行考慮,這會影響性能。

  • a.重複索引

例如如下代碼

CREATE TABLE test (
ID INT NOT NULL PRIMARY KEY,
A INT NOT NULL,
B INT NOT NULL,
UNIQUE(ID),
INDEX(ID)
) ENGINE = InnoDB

一個經驗不足的用戶可能是想創建一個主鍵,先加上唯一限制,然後再加上索引以供查詢使用。事實上,MySQL的唯一限制和主鍵限制都是通過索引實現的,因此,上面的寫法實際上在相同的列上創建了三個重複的索引。通常並沒有理由這樣做,除非是在同一列上創建不同類型的索引來滿足不同的查詢需求。

  • b.冗餘索引

如果創建了索引(A,B),再創建索引(A)就是冗餘索引,因爲這只是前一個索引的前綴索引。
冗餘索引通常發生在爲表添加新索引的時候。例如,有人可能會增加一個新的索引(A,B)而不是擴展已有的索引(A)。還有一種情況是將一個索引擴展爲(A,ID),其中ID是主鍵,對於InnoDB來說主鍵已經包含在二級索引中了,所以這也是冗餘的。

  • c.解決冗餘索引和重複索引的方法很簡單,刪除這些索引就可以了,但首先要找出這樣的索引。

3.10 未使用的索引

對於服務器上一些永遠不用的索引,完全是累贅,建議考慮刪除。

3.11 索引和鎖

InnoDB只有在訪問行的時候纔會對其進行加鎖,而索引能夠減少InnoDB訪問的行數,從而減少鎖的數量。

發佈了104 篇原創文章 · 獲贊 34 · 訪問量 6815
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章