一、索引基礎
如果索引包含多個列,那么列的順序十分重要,因為mysql只能高效的使用所有的最左前綴列。
在mysql中索引是在存儲引擎層實現的,而不是在服務器層。
MyISAM使用前綴壓縮技術,使得索引更小,但是InnoDB則按照原數據格式進行存儲;MyISAM可以通過數據的物理位置(指針)引用被索引的行,而InnoDB則根據主鍵引用被索引的行(指InnoDB的二級索引)。
-
B-Tree對數據是順序組織存儲的,所以很適合查找范圍數據。
image.png
索引對多個值進行排序的依據是CREATE TABLE語句中定義索引時列的順序,如上圖最后兩個條目,兩個人的姓和名都一樣,則根據他們的出生日期來排序 -
索引對如下查詢有效:
- 全值匹配,指和索引中的所有列進行匹配。
- 匹配最左前綴。
- 匹配列前綴,即只匹配某一列的值得開頭部分。如 like "abc%"(注意,通配符%不能放在最前面)。
- 匹配范圍值。
- 精確匹配某一列并范圍匹配另外一列
- 只訪問索引查詢。B-Tree通常可以支持“只訪問索引的查詢”,即查詢只需要訪問索引,而無需訪問數據行,這種查詢叫做“覆蓋索引”,還有一種查詢叫做“回表查詢”
-
關于B-Tree索引的限制:
如果不是按照索引的最左列開始查找,則無法使用索引。
不能跳過索引中的列。如存在(a,b,c)這樣的索引,查詢條件中只有a、c,則在查詢過程中Mysql只能使用索引的a這一列。
如果查詢中有某個列的范圍查詢,則其右邊所有列都無法使用索引優化查詢。如,存在索引(a,b,c),查詢語句為 xxxx WHERE a='123' AND b LIKE 'ww%' AND c='312' ,這個查詢只能使用到索引的前兩列。(大于、小于、等于等操作也是范圍查詢)
哈希索引。存儲引擎都會對所有的索引列計算一個哈希碼,哈希碼索引將所有的哈希碼存儲在索引中,同時在哈希表中保存指向每一個數據行的指針。
-
哈希索引的限制:
- 哈希索引只包含哈希值和行指針,而不存儲字段值,所以不能使用索引中的值來避免讀取行。
- 哈希索引數據并不是按照索引值順序存儲的,所以無法用于排序。
- 哈希索引也不支持部分索引列匹配查找。
- 哈希索引只支持等值比較,如=、IN、<=>,不支持范圍查詢。
InnoDB引擎有一個特殊的功能,叫做“自適應哈希索引”。即,當InnoDB注意到某些索引值被使用的非常頻繁時,它會在內存中基于B-Tree索引之上再創建一個哈希索引,這樣就讓B-Tree索引也具有哈希索引的一些優點。這是一個完全自動的、內部的行為,用戶無法配置,但是也可以關掉。
二、索引的優點
-
索引有如下三個優點:
- 索引大大減少了服務器需要掃描的數據量
- 索引可以幫助服務器避免排序和臨時表
- 索引可以將隨機IO變為順序IO
最常見的B-Tree索引,按照順序存儲數據,所以MySQL可以用來做ORDER BY和GROUP BY操作。
-
“三星系統”的概念:
- 索引將相關的記錄放到一起則獲得一星;
- 如果索引中的數據順序和查找中的排列順序一致,則獲得二星;
- 如果索引中的列包含了查詢中需要的全部列則獲得“三星” 。
三、高性能的索引策略
對于特大型的表,建立和使用索引的代價將隨之增長。這種情況下,則需要一種技術可以直接區分出查詢需要的一組數據,而不是一條記錄一條記錄地匹配(即,分庫、分表、分區技術)。
如果在查詢過程中索引列是表達式的一部分或參數,則整個查詢在執行的過程中不會使用索引。如actor_id是索引:“select actor_id from tb where actor_id+1>10; ”
列區分度 = 不重復的列的行數 / 數據表總行數。一般情況下,區分度高的列適合做索引,因為當使用這些列進行檢索時,可以過濾掉更多的行。
對于聯合索引,索引列的順序意味著索引首先按照最左列進行排序,其次是第二列,等等。所以,索引可以按照升序或者降序進行掃描。
where子句中的排序、分組和范圍條件等其他因素,都可能會對查詢的性能造成非常大的影響。
聚簇索引。它的數據行實際存在在索引的葉子節點上。
InnoDB通過主鍵索引是聚簇索引。如果沒有定義主鍵,InnoDB會選擇一個唯一的非空索引代替,如果沒有這樣的索引,InnoDB會隱式定義一個主鍵索引來作為聚簇索引。
-
聚簇索引的優點:
- 可以把數據保存在一起
- 數據訪問更快
- 用主鍵為唯一查詢條件時,可以實現索引覆蓋掃描。
-
聚簇索引的缺點:
聚簇數據最大限度地提高了IO密集型應用的性能,但是如果數據全部都放在內存中,則訪問的順序就沒那么重要了,聚簇索引也就沒什么優勢了。
插入速度嚴重依賴于插入順序。
更新聚簇索引列的代價很高,因為會強制InnoDB將每個被更新的行移動到新的位置。
基于聚簇索引的表在插入新行,或者主鍵被更新導致需要移動數據的時候,可能面臨“頁分裂”的問題。即,當行的主鍵值要求必須將這一行插入到某個已滿的頁中時,存儲引擎會將該也分裂成兩個頁面來容納該行,這就是一次頁分裂操作。【這一點和前兩點說的基本上都是一回事】
主鍵索引列順序插入很重要,如果主鍵列是按序遞增的,那么在生成主鍵聚簇索引時,總會把新值插入到索引的最后,因此就不會出現頁分裂等情況。當不按照順序插入時,經常要為新的值尋找合適的位置(通常是已經有數據的中間位置),從而增加很多額外的操作(如分配新的空間,數據移位等)。因此一般情況下,主鍵列建議使用自增】
頻繁的頁分裂操作,會使得頁變得稀疏并被不規則的填充,最終數據會有碎片。
二級索引的葉子節點是主鍵值(不是指針),因此當通過二級索引查詢二級索引中不包含的列時,需要回表查詢(即,通過二級索引查找到對應的主鍵,再通過主鍵索引定位最終的列)。
-
如果一個索引包含了所有需要查詢的字段的值,我們就稱之為“覆蓋索引”。即,只需要掃描索引樹就可以完成數據的查詢。如,一個表有id、name、sex三個字段,其中id是主鍵、name列為索引:
- 當 以“select id, name from tb where name='xxx' ”進行查詢時是覆蓋索引,因為name是索引列,id是索引葉子節點,因此只需要掃描name索引樹即可獲取結果。
- 當執行“ select id,name,sex from tb where name='xxx' ”時,就需要回表查詢,因為name索引樹中不包含sex列。
在執行查詢時,Mysql會根據select列和where子句字段來決定如何使用索引。【因此在生產環境中盡量不要使用select * 去查詢】
當一個SQL是“覆蓋索引”查詢的時候,在explain的extra列可以看到“Using index”
在索引列上使用like進行查詢的時候,如果是通配符開頭的like查詢,無法使用到索引。即 xxx like '%asx%'不會使用索引,xxx like 'as%'可以。
MySQL有兩種方式可以生成有序的結果:一種是通過排序操作;一種是按照索引順序掃描 。如果explain出來的type列的值為“index”,則說明MySQL使用了索引掃描來排序。(注意不要和“Using index”混淆了)
MySQL可以使用同一個索引既滿足排序,又用于查找行。因此,如果可能,設計索引時應盡可能地同時滿足這兩種任務。
-
【重要】關于使用索引排序幾個重要的點(order by和group by都適用):
只有當索引的列順序和order by子句的順序完全一致,并且所有列的順序方向(倒序或正序)都一樣時,Mysql才能夠使用索引來對結果做排序。
如果查詢需要關聯多張表,則只有當order by子句引用的字段全部為第一個表時,才能使用索引做排序。如“ SELECT tb1.a,tb2.b FROM tb1 JOIN tb2 ON tb1.c=tb2.c ORDER BY tb1.a, tb2.b”這樣的查詢,在order by子句中使用的字段來自tb1和tb2,這樣排序就無法通過使用索引來實現。
order by子句和查找型查詢的限制一樣,需要滿足索引的最左前綴的要求才能利用索引進行排序。
有一種情況下order by子句即使不滿足索引的最左前綴的要求,也可以利用索引進行排序,那就是前導列為常量時候。如一個表中有(a,b,c)聯合索引,當執行“ xxxxx where a=123 order by b,c”時,前導列a是常數,因此可以使用索引進行排序。
-
幾個示例,如一個表中有(a,b,c)聯合索引:
[1] 【xxxx where a>123 order by a, b】 可以利用索引排序,因為order子句使用的兩列符合索引的最左前綴。
[2] 【xxxx where a=123 order by b desc, c asc】 無法利用索引排序,order by子句列的排序方向不一致。
[3] 【xxxx where a=123 order by b, d】 無法利用索引排序,d不在索引列中。
[4] 【xxxx where a=123 order by c】 無法利用索引排序,只是用了a和c無法組成索引的最左前綴。
[5] 【xxxx where a>123 order by b,c】 無法利用索引排序,a列是范圍查詢,無法通過最左前綴來利用索引。
[6] 【xxxx where a=123 and b in ('y','x') order by c】 無法利用索引排序,b列上有多個等于條件,對于排序來說這是一種范圍查詢,因此無法利用最左前綴來進行排序。
MyISAM使用前綴壓縮來減少索引的大小,從而讓更多的索引可以放進內存中,這在某些情況下能極大的提高性能。其壓縮方法是,先完全保存索引塊中的第一個值,然后將其他值和第一個值進行比較得到相同的前綴的字節數和剩余的不同后綴部分,把這部分存儲起來即可。例如:索引塊中第一個值是“perform”,第二個值是“performance”,那么第二個值的前綴壓縮后存儲的是類似“7,ance”這樣的形式。
因為每個值的壓縮前綴都依賴前面的值,所以MyISAM查找時無法在索引塊使用二分查找,而只能從頭開始掃描。
大多數情況下都不需要冗余的索引,應該盡量擴展已有的索引而不是創建新的索引。若存在重復的索引,mysql優化器在優化查詢的時候也需要逐個地進行考慮,這會影響性能。但有時候出于性能方面的考慮需要冗余索引,因為擴展已有的索引會導致其變得太大,從而影響其他使用該索引的查詢的性能。
索引可以讓查詢鎖定更少的行。
MySQL在執行查詢時,如果無法通過索引過濾掉無效的行,那么在InnoDB檢索到數據并返回執行引擎層后,執行引擎通過where子句對數據進行過濾,此時,explain的extra列會出現“Using where”。
當設計索引時,不要只為現有的查詢考慮需要哪些索引,還需要考慮對查詢進行優化。如果發現某些查詢需要新的索引,但是這個索引又會降低另一些查詢的效率,那么應該想一下,是否能優化原來的查詢。應該通過優化索引和查詢以找到最佳的平衡,而不是閉門造車去設計最完美的索引。
盡可能將需要做范圍查詢的列放到索引的最后。
查詢中避免多個范圍條件,如果MySQL使用了某個索引進行范圍查詢,就無法再使用另一個索引(或者是該索引的后續字段)。