索引是存儲引擎用於快速查找記錄的一種數據結構。索引優化是對查詢性能優化最有效的手段。
1、索引的類型
在MySQL中,索引是在存儲引擎層而不是服務器層實現的。所以沒用統一的索引標准,不同存儲引擎的索引工作方式並不相同。
B-Tree索引
B-Tree索引即使用B-Tree數據結構來存儲數據。B-Tree通常意味着所有值都是按順序存儲的,並且每個葉子頁到根的距離相同。存儲引擎已不同的方式來使用B-Tree索引,性能也各不相同。
可以使用B-Tree索引的查詢類型——全鍵值、鍵值范圍和鍵前綴查找。其中鍵前綴查找只適用於根據最左前綴查找。
B-Tree索引有效的查詢類型:
- 全值匹配——全值匹配是指和索引中的所有列進行匹配。例如:例如有一個索引是key(name,age),該索引可用於查找name=xxx and age=20的人;
- 匹配最左前准——最左前綴即只使用索引的第一列。例如:上邊提到的索引,只使用name=xxx;
- 匹配列前綴——也可以只匹配某一列的的值的開頭部分。例如:上述索引可用於查找所有以J開頭的姓的人;
- 匹配范圍值——例如:上述索引可用於查找姓在allen和bootstrap之間的人;
- 精確匹配某一列並范圍匹配另外一列——例如:第一列全值匹配,第二列范圍匹配;
- 只訪問索引的查詢——例如覆蓋索引。
因為索引樹是有序的,所以還可用於排序查找。
B-Tree索引的使用限制:
- 如果不是按照索引的最左列開始查找,則索引失效。例如:索引key(name,age),無法查找where age=20 and name='tom'的人;
- 不能跳過索引中的列。例如索引key(name,age,address),如果查找where name="xxx" and address="xxx",則只能使用第一列索引;
- 如果查詢中有某個列的范圍查詢,則其右邊所有列都無法使用索引。例如:索引key(name,age,address),如果查詢條件where name like 'S%' and age=20,這個查詢只能使用索引的第一列。
哈希索引
哈希索引基於哈希表實現,只有精確匹配索引的所有列的查詢才有效。在MySQL中,只有Memory引擎顯示支持哈希索引,這也是Memory引擎的默認索引類型。
對於每一行數據,存儲引擎都會對所有的索引列計算一個哈希碼,如果多個列的哈希碼相同,索引會以鏈表的方式存放多個記錄指針到同一個哈希條目中。哈希索引將所有的哈希碼存儲在索引中,同時在哈希表中保存指向每個數據行的指針。
哈希索引使用限制:
- 哈希索引值存儲哈希值和行指針,而不存儲字段值,所以不能使用索引中的值來避免讀取行數據;
- 哈希索引數據並不是按照索引值順序存儲的,所以也就無法用於排序;
- 哈希索引不支持部分索引列匹配查詢,因為哈希索引始終是使用索引列的全部內容來計算哈希值的;
- 哈希索引只支持等值比較查詢,包括=、IN()、<=> 。不支持任何范圍查詢;
- 訪問哈希索引的數據非常快,除非出現哈希沖突,此時存儲引擎需要遍歷鏈表中所有行指針,逐行進行比較;
- 如果哈希沖突非常多的話,一些索引維護操作的成本也會非常高。
InnoDB引擎有一個特殊的功能叫做“自適應哈希索引”。當InnoDB注意到某些索引值被使用得非常頻繁時,它會在內存中基於B-Tree索引之上再創建一個哈希索引,這樣B-Tree索引也具有可哈希索引的一些特性。這是一個自發的內部行為,用戶無法控制,但用戶可以關閉次功能。
創建自定義哈希索引
如果存儲引擎不支持哈希索引創建一個偽哈希索引,可以自己。思路:在B-Tree索引的基礎上創建一個偽哈希索引。
空間數據索引(略)
全文索引(略)
其他索引類別(略)
2、索引的優點
索引的三大優點:
- 索引大大減少了服務器需要掃描的數據量;
- 索引可以幫助服務器避免排序和臨時表;
- 索引可以將隨機I/O變為順序I/O。
三星系統——如何評價一個索引是否符合某個查詢
索引將相關的記錄放到一起則獲得一星;如果索引中的數據順序和查找中的排序順序一致則獲得兩星;如果索引中的列包含了查詢中的所有列則獲得三星。
3、高性能的索引策略
- 獨立的列——索引列不能是表達式的一部分,也不能是函數的參數。例如:SELECT actor_id FROM actor WHERE actor_id + 1 = 5;或者SELECT actor_id FROM actor WHERE f(actor_id) = 5;
- 前綴索引和索引選擇性——有時候需要索引很長的字符列,這會讓索引變得很大且很慢。此時可以有兩個策略,一個是自定義哈希索引,另一個就是前綴索引;
- 前綴索引能大大節約索引空間,從而提高索引效率,但這樣也會降低索引的選擇性(索引選擇性——不重復的索引值和數據表記錄總數的比值);
- 索引前綴長度的選擇——計算法。例如:LELECT COUNT(DISTINCT city)/COUNT(*) AS sel1, COUNT(DISTINCT LEFT(city, 3))/COUNT(*) AS sel2, ...; 如果前綴的選擇性接近sel1就可以使用了。有時候只看平均選擇型也不靠譜,還需要做進一步判斷。
- 缺點:MySQL無法使用前綴索引做ORDER BY和GROUP BY,也無法使用前綴索引做覆蓋掃描;
- 有時候也可以使用前綴索引——可將對應列的字符串反序存儲,並創建前綴索引。
- 多列索引——為多列創建合適的索引
- 多列索引。例如:key(col1, col2, col3);
- MySQL5.0之后的版本引入了“索引合並”的策略,一定程度上可以使用表上的多個單列索引來定位表中的行;
- 索引合並策略有時候是一種優化后的結果,但實際上更說明表上的索引建得很糟糕。
- 當出現服務器對多個索引做相交操作時(多個AND),通常意味着需要一個包含相關列的多列索引,而不是多個獨立的單列索引;
- 當服務器需要對多個索引做聯合操作時(多個OR),通常需要耗費大量的CPU和內存在算法的緩存、排序和合並上。
- 選擇合適的索引順序
- 正確的索引順序依賴於使用該索引的查詢,並且同時需要考慮如何更好的滿足排序和分組的需要;
- 索引可以按照升序或者降序進行掃描,以滿足精確符合列順序的ORDER BY 、GROUP BY和DISTINCT等子句的查詢需求;
- 索引列順序的選擇——在不考慮分組和排序的情況下,將選擇性最高的列放到索引最前面(經驗法則);
- 避免隨機I/O和排序;
- 對於某些特殊用戶和分組,避免其使用普通的索引查詢。
- 聚簇索引——聚簇索引並不是一種單獨的索引類型,而是一種數據存儲方式
- 覆蓋索引
- 使用索引掃描排序——MySQL有兩種方式可以生成有序結果:通過排序操作;按照索引順序掃描。
- 只有當索引的列順序和ORDER BY子句的順序完全一致,並且所有列的排序方向(升序/降序)都一樣時,MySQL才能使用索引來對結果做排序;
- 當查詢需要關聯多張表時,只有當ORDER BY子句引用的字段全部來自第一張表時,才能使用索引排序;
- ORDER BY子句中的字段需要滿足索引的最左前綴的要求,才能使用索引排序;
- 當索引的前導列為常量時,ORDER BY子句可以不滿足索引的最左前綴要求也能使用索引排序。例如:key(rental_date, inventory_id, customer_id);... where rental_data='2018-01-08' ORDER BY inventory_id DESC;
- 壓縮(前綴壓縮)索引(略)
4、維護索引和表
維護表有三個目的:找到並修復損壞的表;維護准確的索引統計信息;減少碎片。
- 更新索引統計信息——MySQL的查詢優化器會通過兩個API來了解存儲引擎的索引值的分布信息,已決定如何使用索引信息。(1)、records_in_range();(2)、info()。如果存儲引擎向優化器提供的索引統計信息不准確,就會導致優化器做出錯誤的優化決定,這會嚴重影響查詢性能。可通過執行ANALYZE TABLE 來重新生成統計信息以解決這個問題。
- 減少索引和數據的碎片
- B-Tree索引可能會碎片化,碎片化的索引可能會以很差或無序的方式存儲在磁盤上,這會降低查詢效率;
- 表數據存儲也可能碎片化。主要有行碎片、行間碎片、剩余空間碎片三種。對於MyISAM表,這三類碎片都可能發生,但InnoDB不會出現短小的行碎片,InnoDB會移動短小的行,並重寫到一個片段中。
- 【維護方法】可通過執行POTIMIZE TABLE或者導出再導入來重新整理數據;對於那些不支持POTIMIZE TABLE命令的引擎,可以執行ALTER TABLE操作來重建表。只需要將表的存儲引擎改為當前的引擎即可。例如:ALTER TABLE <table> ENGINE=<engine>;