MySQL中的索引

MySQL支持諸多存儲引擎,而各種存儲引擎對索引的支持也各不相同,因此MySQL數(shù)據(jù)庫支持多種索引類型,如BTree索引,哈希索引,全文索引等等。為了避免混亂,本文將只關注于BTree索引,因為這是平常使用MySQL時主要打交道的索引。

關于全文索引,在另一篇文章中有所提及。

什么是索引?

索引(Index)是幫助MySQL高效獲取數(shù)據(jù)的數(shù)據(jù)結(jié)構。提取句子主干,就可以得到索引的本質(zhì):索引是一種數(shù)據(jù)結(jié)構。

為了提高數(shù)據(jù)庫的查詢性能,需要找出比順序查找更好的查詢算法。而這些優(yōu)化的查詢算法往往以來特定的數(shù)據(jù)結(jié)構才能實現(xiàn),比如二叉樹。

因此,在數(shù)據(jù)之外,數(shù)據(jù)庫系統(tǒng)還維護著滿足特定查找算法的數(shù)據(jù)結(jié)構。這些數(shù)據(jù)結(jié)構以某種方式引用(指向)數(shù)據(jù),這樣就可以在這些數(shù)據(jù)結(jié)構上實現(xiàn)高級查找算法。這種數(shù)據(jù)結(jié)構就是索引。

image.png

MySQL中的索引

實際的數(shù)據(jù)庫系統(tǒng)通常使用B(B+)樹作為索引的實現(xiàn)。B樹是一種矮胖的M叉查找樹,是磁盤存儲較為緩慢這一缺陷的解決方案,廣泛使用在文件系統(tǒng)中。

關于什么是B(B+)樹,可以參考另一篇文章

MySQL中的索引實現(xiàn)

在MySQL中,索引屬于存儲引擎級別的概念,不同存儲引擎對索引的實現(xiàn)方式是不同的。但是無論是MyISAM還是InnoDB,都采用了B+樹的索引實現(xiàn)。

MyISAM索引實現(xiàn)

MyISAM使用B+樹作為索引結(jié)構,葉結(jié)點的data域存放的是數(shù)據(jù)記錄的地址。

image.png

這里設表一共有三列,假設我們以Col1為主鍵,則圖8是一個MyISAM表的主索引(Primary key)示意。可以看出MyISAM的索引文件僅僅保存數(shù)據(jù)記錄的地址。在MyISAM中,主索引和輔助索引(Secondary key)在結(jié)構上沒有任何區(qū)別,只是主索引要求key是唯一的,而輔助索引的key可以重復。如果我們在Col2上建立一個輔助索引,則此索引的結(jié)構如下圖所示:

image.png

同樣也是一顆B+Tree,data域保存數(shù)據(jù)記錄的地址。因此,MyISAM中索引檢索的算法為首先按照B+Tree搜索算法搜索索引,如果指定的Key存在,則取出其data域的值,然后以data域的值為地址,讀取相應數(shù)據(jù)記錄。

MyISAM的索引方式也叫做“非聚集”的,之所以這么稱呼是為了與InnoDB的聚集索引區(qū)分。

InnoDB索引實現(xiàn)

雖然InnoDB也使用B+Tree作為索引結(jié)構,但具體實現(xiàn)方式卻與MyISAM截然不同。

第一個重大區(qū)別是InnoDB的數(shù)據(jù)文件本身就是索引文件。從上文知道,MyISAM索引文件和數(shù)據(jù)文件是分離的,索引文件僅保存數(shù)據(jù)記錄的地址。而在InnoDB中,表數(shù)據(jù)文件本身就是按B+Tree組織的一個索引結(jié)構,這棵樹的葉結(jié)點data域保存了完整的數(shù)據(jù)記錄。這個索引的key是數(shù)據(jù)表的主鍵,因此InnoDB表數(shù)據(jù)文件本身就是主索引。

image.png

上圖為InnoDB主索引(同時也是數(shù)據(jù)文件)的示意圖,可以看到葉結(jié)點包含了完整的數(shù)據(jù)記錄。這種索引叫做聚集索引。因為InnoDB的數(shù)據(jù)文件本身要按主鍵聚集,所以InnoDB要求表必須有主鍵(MyISAM可以沒有),如果沒有顯式指定,則MySQL系統(tǒng)會自動選擇一個可以唯一標識數(shù)據(jù)記錄的列作為主鍵,如果不存在這種列,則MySQL自動為InnoDB表生成一個隱含字段作為主鍵,這個字段長度為6個字節(jié),類型為長整形。

第二個與MyISAM索引的不同是InnoDB的輔助索引data域存儲相應記錄主鍵的值而不是地址。換句話說,InnoDB的所有輔助索引都引用主鍵作為data域。例如,圖11為定義在Col3上的一個輔助索引:

image.png

這里以英文字符的ASCII碼作為比較準則。聚集索引這種實現(xiàn)方式使得按主鍵的搜索十分高效,但是輔助索引搜索需要檢索兩遍索引:首先檢索輔助索引獲得主鍵,然后用主鍵到主索引中檢索獲得記錄。

了解不同存儲引擎的索引實現(xiàn)方式對于正確使用和優(yōu)化索引都非常有幫助,例如知道了InnoDB的索引實現(xiàn)后,就很容易明白為什么不建議使用過長的字段作為主鍵,因為所有輔助索引都引用主索引,過長的主索引會令輔助索引變得過大。再例如,用非單調(diào)的字段作為主鍵在InnoDB中不是個好主意,因為InnoDB數(shù)據(jù)文件本身是一顆B+Tree,非單調(diào)的主鍵會造成在插入新記錄時數(shù)據(jù)文件為了維持B+Tree的特性而頻繁的分裂調(diào)整,十分低效,而使用自增字段作為主鍵則是一個很好的選擇。

索引使用策略及優(yōu)化

最左前綴原理與相關優(yōu)化

高效使用索引的首要條件是知道什么樣的查詢會使用到索引,這個問題和B+Tree中的“最左前綴原理”有關,下面通過例子說明最左前綴原理。

什么是聯(lián)合索引?在上文中,我們都是假設索引只引用了單個的列,實際上,MySQL中的索引可以以一定順序引用多個列,這種索引叫做聯(lián)合索引,一般的,一個聯(lián)合索引是一個有序元組<a1, a2, …, an>,其中各個元素均為數(shù)據(jù)表的一列。注意,這里并不是一個嚴格定義。另外,單列索引可以看成聯(lián)合索引元素數(shù)為1的特例。

當按照索引中所有列進行精確匹配(這里精確匹配指“=”或“IN”匹配)時,索引可以被用到。這里有一點需要注意,理論上索引對順序是敏感的,但是由于MySQL的查詢優(yōu)化器會自動調(diào)整where子句的條件順序以使用適合的索引,效果是一樣的。

當查詢條件精確匹配索引的左邊連續(xù)一個或幾個列時,索引可以被用到,但是只能用到一部分,即條件所組成的最左前綴。這是最左前綴匹配。

索引選擇性與前綴索引

因為索引雖然加快了查詢速度,但索引也是有代價的:索引文件本身要消耗存儲空間,同時索引會加重插入、刪除和修改記錄時的負擔,另外,MySQL在運行時也要消耗資源維護索引,因此索引并不是越多越好。一般兩種情況下不建議建索引。

第一種情況是表記錄比較少,例如一兩千條甚至只有幾百條記錄的表,沒必要建索引,讓查詢做全表掃描就好了。

另一種不建議建索引的情況是索引的選擇性較低。所謂索引的選擇性(Selectivity),是指不重復的索引值(也叫基數(shù),Cardinality)與表記錄數(shù)(#T)的比值:Index Selectivity = Cardinality / #T

選擇性(Selectivity)的取值范圍為(0, 1],選擇性越高的索引價值越大,這是由B+Tree的性質(zhì)決定的。

有一種與索引選擇性有關的索引優(yōu)化策略叫做前綴索引,就是用列的前綴代替整個列作為索引key,當前綴長度合適時,可以做到既使得前綴索引的選擇性接近全列索引,同時因為索引key變短而減少了索引文件的大小和維護開銷。

前綴索引兼顧索引大小和查詢速度,但是其缺點是不能用于ORDER BY和GROUP BY操作,也不能用于Covering index(即當索引本身包含查詢所需全部數(shù)據(jù)時,不再訪問數(shù)據(jù)文件本身)。

InnoDB的主鍵選擇與優(yōu)化

在使用InnoDB存儲引擎時,如果沒有特別的需要,請永遠使用一個與業(yè)務無關的自增字段作為主鍵。這是從數(shù)據(jù)庫索引優(yōu)化的角度來選擇的。

InnoDB的索引實現(xiàn),InnoDB使用聚集索引,數(shù)據(jù)記錄本身被存于主索引(一顆B+Tree)的葉子節(jié)點上。這就要求同一個葉子節(jié)點內(nèi)(大小為一個內(nèi)存頁或磁盤頁)的各條數(shù)據(jù)記錄按主鍵順序存放,因此每當有一條新的記錄插入時,MySQL會根據(jù)其主鍵將其插入適當?shù)墓?jié)點和位置,如果頁面達到裝載因子(InnoDB默認為15/16),則開辟一個新的頁(節(jié)點)。

如果表使用自增主鍵,那么每次插入新的記錄,記錄就會順序添加到當前索引節(jié)點的后續(xù)位置,當一頁寫滿,就會自動開辟一個新的頁。如下圖所示:

image.png

這樣就會形成一個緊湊的索引結(jié)構,近似順序填滿。由于每次插入時也不需要移動已有數(shù)據(jù),因此效率很高,也不會增加很多開銷在維護索引上。

如果使用非自增主鍵(如果身份證號或?qū)W號等),由于每次插入主鍵的值近似于隨機,因此每次新紀錄都要被插到現(xiàn)有索引頁得中間某個位置:

image.png

此時MySQL不得不為了將新記錄插到合適位置而移動數(shù)據(jù),甚至目標頁面可能已經(jīng)被回寫到磁盤上而從緩存中清掉,此時又要從磁盤上讀回來,這增加了很多開銷,同時頻繁的移動、分頁操作造成了大量的碎片,得到了不夠緊湊的索引結(jié)構,后續(xù)不得不通過OPTIMIZE TABLE來重建表并優(yōu)化填充頁面。

參考
https://blog.csdn.net/waeceo/article/details/78702584

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容