高性能的索引策略
獨立的列
以下兩個錯誤的語句,需要始終將索引列單獨放在比較符號的一側
SELECT actor_id FROM dskils.actor WHERE actor.id + 1 = 5;
SELECT ... WHERE TO_DAYS(CURRENT_DATE) - TO_DAYS(date_col) <= 10;
前綴索引和索引的選擇性
有時候需要索引很長的字符列,這會讓索引變得大且慢.一個策略是前面提到過的模擬哈希索引.但有時候還不夠
??通常可以索引開始的部分字符,這樣可以大大節約索引空間,提高索引效率.但是這樣也會降低索引的選擇性(cardinality).索引的選擇性越高查詢效率越高,唯一索引的選擇性最高為1.
??一般情況下,某個列前綴的選擇性也是足夠高的,足以滿足查詢性能.但是對于 BLOB,TEXT 或者很長的 VARCHAR 類型的列,必須使用前綴索引,因為MySQL 不允許索引這些列的完整長度.
??訣竅在于要選擇足夠長的前綴索引以保證較高的選擇性,同事又不能太長(節約空間).前綴應該足夠長,以使得前綴索引的選擇性接近于索引的整個列.
計算合適的前綴長度的另外一個方法就是計算完整列的選擇性,并使前綴的選擇性接近于完整列的選擇性。下面展示如何計算完整列的選擇性:
多列索引
選擇合適的索引列順序
當不需要考慮排序和分組時,最好將選擇性最高的列放到索引的最前列.具體的可以測試得出.
聚簇索引
聚簇索引并不是一種單獨的索引類型,而是一種數據存儲方式.InnoDB 的聚簇索引實際上在同一結構中保存了 B-Tree 索引和數據行.
當表有聚簇索引時,他的數據行實際上存放在索引的葉子頁中.術語"聚簇"表示數據行和相鄰的兼職緊湊的存儲在一起.因為無法同時把數據行存放在兩個不同的地方,所以一個表只有一個聚簇索引(不過覆蓋索引可以模擬多個聚簇索引的情況).
聚集的數據有以下重要的優點:
- 可以把相關的數據保存在一起.
- 數據訪問更快.因為聚簇索引將索引和數據存放在用一個 B-Tree 中.
- 使用覆蓋索引掃描的查詢可以直接使用節點中的主鍵值
也有以下缺點:
- 聚簇索引可以提高 I/O 的性能.如果將數據全部放在內存中將沒有使用聚簇索引的必要.
- 插入速度嚴重依賴插入順序.
- 更新聚簇索引的代價很高.
- 基于聚簇索引的表在插入新行,或者主鍵被更新需要移動行的時候可能面臨葉分裂的問題.葉分裂會導致表占用更多的磁盤空間
- 聚簇索引可能導致全表掃描變慢
- 二級索引可能比想象的更大
- 二級索引訪問需要兩次索引查找,而不是一次
覆蓋索引
如果索引的葉子節點中已經包含要查詢的數據,那么還有什么必要再回表查詢呢?如果一個索引包含(或者說覆蓋)所有需要查詢的字段的值,我們就稱為覆蓋索引.
覆蓋索引有以下優點:
- 索引條目通常遠小于數據行大小,所以如果只需要讀取索引,就極大的減少數據訪問量。這對MyISAM尤其正確,因為MyISAM能壓縮索引以變得更小.
- 因為索引是按照順序存儲的(單頁),所以對于I/O密集型的范圍查詢會比隨機從磁盤讀取每一行數據的I/O要少很多
- 由于InnoDB的聚簇索引,覆蓋索引對InnoDB特別有用。InnoDB的二級索引在葉子節點中保存了行的主鍵值,所以如果二級主鍵能夠覆蓋查詢,則可以避免對主鍵索引的二次查詢
覆蓋索引必須要存儲索引列的值,而哈希索引、空間索引、和全文索引都不能存儲列的值,所以MySQL只能使用B-Tree索引做覆蓋索引.
當發起一個索引覆蓋的查詢時,在EXPLAIN的Extra列可以看到Usingindex的信息,另外需要注意觸發覆蓋索引的條件
使用索引來做排序
MySQL 有兩種方式可以生成有序的結果:通過排序操作;或者按索引順序掃描;如果 EXPLAIN 出來的 type 的值為 index, 則說明 MYSQLS使用了索引掃描來做排序.
- 排序操作: 將查找出來的結果使用排序算法進行排序
- 按索引順序掃描:ORDER BY語句后跟著一個被索引的列,如此一來索引的順序就是索引對應記錄的順序,這樣直接順著索引一直往下讀取記錄即可得到有序的結果。
- 隨機IO操作會大大拖慢執行速度,導致按照索引掃描的執行速度反而要比排序操作要慢。因此,在考慮使用按照索引掃描的方式去獲得有序結果,那么設計索引時必須要考慮索引覆蓋的情況
只有當索引的列順序和 ORDER BY 字句的順序完全一致,并且所有列的排列方向(倒序或者正序)都一樣, MySQL 才能夠使用索引來對結果做排序.如果查詢需要關聯多張表時,則只有當 ORDER BY 子句引用的字段全部為第一個表時,才能使用索引做排序.ORDER BY子句和查找型查詢的限制是一樣的:需要滿足索引的最左前的要求;否則, MySQL 都需要執行排序操作,從而無法利用索引排序.
有一種例外,ORDER BY后跟的字段可以不滿足最左前綴原則:當前導量為常量的時候。這樣可以彌補索引的不足.
壓縮(前綴)索引
MyISAM 使用前綴壓縮來減少索引的大小,從而讓更多的索引可以放入內存,默認只壓縮字符串.
MyISAM 壓縮每一個索引塊的方法是先保存索引塊中的第一個值,然后將其他值和第一個值進行比較得到相同前綴的字節數和剩余的不同后綴部分,把這部分存儲起來即可.
壓縮快可以使用更少的空間,代價是某些操作可能更慢.
冗余和重復索引
MySQL 允許在相同的列上創建多個索引,無論是有意的還是無意的.MySQL 需要單獨維護重復的索引,并且優化器在優化查詢的時候也需要逐個的進行查詢考慮,這會影響性能.
重復索引是指在相同的列上按照相同的順序創建的相同類型的索引.
冗余索引通常發生在為表添加新索引的時候.例如有索引(A,B),又添加(A),或者(A,ID),因為 InnoDB 主鍵列已經包含在二級索引中.
大多數時候都不需要冗余索引,但是如果擴展索引會導致已有的索引變得太大從而影響查詢性能,可以新建索引.
未使用的索引
可以使用工具幫助定位未使用的索引.例如 Percona Toolkit中的 pt-index-usage,該公布工具不僅可以讀取查詢日志并且對日志中的每條查詢進行 EXPALIN 操作.
索引和鎖
索引可以讓查詢鎖定更少的行.雖然 InnoDB 的行鎖效率很高,內存使用很少,但是鎖定行的時候任然會帶來額外的開銷;其次鎖定超過需要的行會增加鎖爭用并減少并發性.
InnoDB 只有在訪問行的時候才會對其加鎖,而索引能減少 InnoDB 訪問的行數,從而減少鎖的數量.但著只有當 InnoDB 在存儲引擎層能夠過濾掉所有不需要的行時才有效.否則數據傳輸到服務器層以后, MySQL服務器才能應用 WHERE 子句,這是已經無法避免行鎖定了,只有到適當的時候才會釋放.
InnoDB 在二級索引上使用共享(讀)鎖,但訪問主鍵索引需要排他(寫)鎖.這消除了使用覆蓋索引的可能性,并且使得 SELECT FOR UPDATE 比 LOCK IN SHARE MODE或非鎖定查詢要慢得多.