高性能Mysql-創建高性能索引

索引(在MySQL中也叫做 “鍵(key)”)是存儲引擎用於快速找到記錄的一種數據結構。索引對於良好的性能非常關鍵。 尤其是當表中的數據量越來越大時, 索引對性能的影響愈發重要。 在數據量較小且負載較低時, 不恰當的索引對性能的影響可能還不明顯, 但當數據量逐漸增大時, 性能則會急劇下降。索引優化應該是對查詢性能優化最有效的手段了。索引能夠輕易將查詢性能提高几個數量級, “最優” 的索引有時比一個 “好的” 索引性能要好兩個數量級。

1、索引基礎

在Mysql中,存儲引擎先在索引上查找到對應的值,然後根據匹配的索引記錄找到對應的數據行。例如:

SELECT first_name FROM sakila.actor WHERE actor_id = 5;

如果在actor_id列上建有索引, 則MySQL將使用該索引找到actor_id爲5的行, 也就是說, MySQL先在索引上按值進行查找, 然後返回所有包含該值的數據行。

索引可以包含一個或多個列的值。 如果索引包含多個列, 那麼列的順序也十分重要, 因爲MySQL只能高效地使用索引的最左前綴列。 創建一個包含兩個列的索引, 和創建兩 個只包含一列的索引是大不相同的, 下面將詳細介紹。

1.1 索引的類型

索引有很多種類型, 可以爲不同的場景提供更好的性能。 在MySQL中, 索引是在存儲引擎層而不是服務器層實現的。 所以, 並沒有統一的索引標準:不同存儲引擎的索引的工作方式並不一樣, 也不是所有的存儲引擎都支持所有類型的索引。 即使多個存儲引擎支持同一種類型的索引, 其底層的實現也可能不同。

B-Tree索引

當人們談論索引的時候, 如果沒有特別指明類型, 那多半說的是B-Tree索引, 它使用B-Tree數據結構來存儲數據。大多MySQL引擎都支持這種索引。不過,底層的存儲引擎也可能使用不同的存儲結構,例如,NDB集羣存儲引擎內部實際上使用了T-Tree結構存儲這種索引,即使其名字是BTREE。下圖展示了B-Tree索引的抽象設計,大致反應了innodb是如何工作的。

B-Tree索引能夠加快訪問數據的速度,因爲存儲引擎不再需要進行全表掃描來獲取需要的數據,取而代之的是從索引的根節點開始進行搜索。根節點的槽中存放了指向子節點的指針,存儲引擎根據這些指針向下層查找。

可以使用B-Tree索引的查詢類型。 B-Tree索引適用於全鍵值、 鍵值範圍或鍵前綴查找。其中鍵前綴查找只適用於根據最左前綴的查找。

哈希索引

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

在MySQL中, 只有Memory引擎顯式支持哈希索引。這也是Memory引擎表的默認索 引類型,Memory引擎同時也支持B-Tree索引。

因爲索引自身只需存儲對應的哈希值,所以索引的結構十分緊湊,這也讓哈希索引查找的速度非常快。然而,哈希索引也有它的限制:

•    哈希索引只包含哈希值和行指針,而不存儲字段值,所以不能使用索引中的值來避免讀取行。不過,訪問內存中的行的速度很快,所以大部分情況下這一點對性能的 影響井不明顯。
•    哈希索引數據井不是按照索引值順序存儲的,所以也就無法用於排序。
•    哈希索引也不支持部分索引列匹配查找,因爲哈希索引始終是使用索引列的全部內容來計算哈希值的。例如,在數據列(A,B)上建立哈希索引,如果查詢只有數據列A, 則無法使用該索引。

•    哈希索引只支持等值比較查詢,包括=、 IN() 、<=>(注意。和〈=〉是不同的操作)。 也不支持任何範圍查詢,例如WHERE price> 100.
•    訪問哈希索引的數據非常快,除非有很多哈希衝突(不同的索引列值卻有相同的哈希值)。 當出現哈希衝突的時候,存儲引擎必須遍歷鏈表中所有的行指針,逐行進行比較,直到找到所有符合條件的行。
•    如果哈希衝突很多的話,一些索引維護操作的代價也會很高。 例如,如果在某個選擇性很低(哈希衝突很多) 的列上建立哈希索引,那麼當從表中刪除一行時, 存儲引擎需要遍歷對應哈希值的鏈表中的每一行,找到井刪除對應行的引用,衝突越多, 代價越大。

空間數據索引

MyISAM表支持空間索引, 可以用作地理數據存儲。 和B-Tree索引不同, 這類索引無須前綴查詢。 空間索引會從所有維度來索引數據。 查詢時, 可以有效地使用任意維度來組合查詢。 必須使用MySQL的GIS相關函數如MBRCONTAINS()等來維護數據。 MySQL的GIS支持並不完善, 所以大部分人都不會使用這個特性。 開源關係數據庫系統中對GIS的解決方案做得比較好的是PostgreSQL的PostGIS。

全文索引

全文索引是一種特殊類型的索引,它查找的是文本中的關鍵詞,而不是直接比較索引中的值。全文搜索和其他幾類索引的匹配方式完全不一樣。它有許多需要注意的細節,如停用詞、詞幹和複數、布爾搜索等。全文索引更類似於搜索引擎做的事情,而不是簡單的WHERE條件匹配。

2、索引的優點

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

3、高性能索引策略

3.1 獨立的列

我們通常會看到一些查詢不當的使用索引,或者使得mysql無法使用已有的索引。如果查詢中的列不是獨立的,則mysql就不會使用索引。“獨立的列”是指索引不能是表達式的一部分,也不能是函數的參數。

例如:SELECT actor_id FRO問sakila.actor WHERE actor_id + 1 = s;這個無法使用索引。

3.2 前綴索引和索引選擇性

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

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

前綴索引是一種能使索引更小、更快的有效辦法,但另一方面也有其缺點:Mysql無法使用前綴索引做order by和group by,也無法使用前綴索引做覆蓋掃描。

3.3 多列索引

很多人對多列索引的理解都不夠。 一個常見的錯誤就是, 爲每個列創建獨立的索引, 或者按照錯誤的順序創建多列索引。

在多個列上建立獨立的單列索引大部分情況下並不能提高MySQL的查詢性能。 MySQL5.0和更新版本引入了一種叫 “索引合併” (index merge)的策略, 一定程度上可以使用表上的多個單列索引來定位指定的行。 更早版本的MySQL只能使用其中某一個單列索引, 然而這種情況下沒有哪一個獨立的單列索引是非常有效的。

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

•    當出現服務器對多個索引做相交操作時(通常有多個AND條件), 通常意味着需要一 個包含所有相關列的多列索引, 而不是多個獨立的單列索引。
•    當服務器需要對多個索引做聯合操作時(通常有多個OR條件), 通常需要豔費大量CPU和內存資源在算棒的緩存、 排序和合並操作上。 特別是當其中有些索引的選擇性不高, 需要合井掃描返回的大量數據的時候。

•   更重要的是, 優化器不會把這些計算到 “查詢成本” (cost)中,優化器只關心隨機頁面讀取。這會使得查詢的成本被 “低估”,導致該執行計劃還不如直接走全表掃描。這樣做不但會消耗更多的CPU和內存資源, 還可能會影響查詢的井發性, 但如果是單獨運行這樣的查詢則往往會忽略對井發性的影響。 通常來說, 還不如像在MySQL 4.1或者更早的時代一樣, 將查詢改寫成 UNION 的方式往往更好。

3.4 選擇合適的索引列順序

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

對於如何選擇索引的列順序有一個經驗法則:將選擇性最高的列放到索引最前列。

3.5 聚簇索引

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

聚簇主鍵可能對性能有幫助, 但也可能導致嚴重的性能問題。 所以需要仔細地考慮聚簇索引, 尤其是將表的存儲引擎從InnoDB改成其他引擎的時候(反過來也一樣)。
聚集的數據有一些重要的優點:

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

如果在設計表和查詢時能充分利用上面的優點, 那就能極大地提升性能。 同時, 聚簇索引也有一些缺點:

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

3.6 覆蓋索引

如果一個索引覆蓋或包含所有需要查詢的字段的值,我們就稱之爲覆蓋索引。覆蓋索引是非常有用的工具,能夠極大地提高性能。

在選擇索引和編寫利用這些索引的查詢時, 有如下三個原則始終需要記住:

1.    單行訪問是很慢的。 特別是在機械硬盤存儲中(SSD的隨機I/O要快很多, 不過這一點仍然成立)。 如果服務器從存儲中讀取一個數據塊只是爲了獲取其中一行, 那麼就浪費了很多工作。 最好讀取的塊中能包含儘可能多所需要的行。 使用索引可以創建位置引用以提升效率。
2.    按順序訪問範圍數據是很快的,這有兩個原因。 第一,順序I/O不需要多次磁盤尋道, 所以比隨機I/O要快很多(特別是對機械硬盤)。 第二, 如果服務器能夠按需要順序 讀取數據, 那麼就不再需要額外的排序操作, 井且GROUP BY查詢也無須再做排序和 將行按組進行聚合計算了。
3.    索引覆蓋查詢是很快的。 如果一個索引包含了查詢需要的所有列, 那麼存儲引擎就不需要再回表查找行。 這避免了大量的單行坊問, 而上面的第1點已經寫明單行訪問是很慢的。

 

 

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