本文連結:https://www.cnblogs.com/ibigboy/p/16198243.html
之前的一篇《MySQL索引底層資料結構及原理深入分析》很受讀者歡迎,成功地幫大家揭開了索引的神祕面紗,有讀者留言說分不清各種索引的概念,希望能講一下。確實,資料庫中索引種類很多,如聚集索引、複合索引、二級索引、唯一索引…你是不是也搞得不是太清楚,那麼今天就帶大家一起看下索引的分類及相關概念。
索引分類
從資料結構角度可分為B+樹索引、雜湊索引、以及全文(FULLTEXT)索引(現在MyISAM和InnoDB引擎都支援)和R-Tree索引(用於對GIS資料型別建立SPATIAL索引);
從物理儲存角度可分為聚集索引(clustered index)、非聚集索引(non-clustered index);
從邏輯角度可分為主鍵索引、普通索引(輔助索引、二級索引等等);
從列的個數角度可以分為單列索引、多列索引(或者叫複合索引、聯合索引);
從唯一性角度可以分為唯一索引、非唯一索引。
一個索引從不同的角度看可以屬於不同的索引。例如一個索引可以是多列索引也可以是唯一索引,也可以是輔助索引。
本文連結:https://www.cnblogs.com/ibigboy/p/16198243.html
從資料結構角度
B+樹索引結構
MySQL採用B+Tree作為索引和資料的儲存結構,如下圖1所示,在這棵樹中,非葉子節點只儲存索引,而且索引是冗餘的,非葉子節點中兩個索引中間儲存的是下一個資料頁的地址。在InnoDB儲存引擎中,預設使用主鍵建立索引樹,其葉子節點中每個資料項包括主鍵和主鍵所在行的所有列資料或資料地址。
注意:MySQL中一個資料頁大小是16K,每頁存的資料個數有限,和列數及資料型別有關。
圖1
具體分析過程可以參考之前的文章,MySQL索引底層原理及資料結構深入分析,連結地址:https://mp.weixin.qq.com/s/1C1QvOvKs_yAf81GiqGoNA
HASH索引
hash索引的查詢效率要高於B+Tree,但是99%的情況都是用B+Tree,極少情況下用hash。hash結構索引的原理:對索引欄位做hash計算,把運算的結果值和這一行資料所在磁碟地址做對映。
那麼,使用HASH索引時,對於資料查詢,HASH索引做一次hash運算就可以在hash對映表裡快速找到這一行的磁碟檔案地址。一次hash就可以快速定位到索引所在行的磁碟檔案地址!
即使表有一億個資料,按這種演算法,那也就可能經歷一次hash運算就可以快速找到某頁任意一行資料元素的所在的磁碟檔案地址,時間複雜度是O(1),那比B+Tree快得多啊!那為啥99%的情況下都是用B+Tree而不用hash呢?
原因是如果使用範圍查詢,hash就沒有用武之地了,在業務中範圍查詢是很常用的,但HASH索引不支援,所以基本就不怎麼用hash這種資料結構。hash索引適用於等值查詢的場景。
全文索引
MySQL 5.6 以前的版本,只有 MyISAM 儲存引擎支援全文索引。從InnoDB 1.2.x版本開始,InnoDB儲存引擎開始支援全文檢索,對應的MySQL版本是5.6.x系列。
但不管什麼引擎,只有欄位的資料型別為 char、varchar、text 及其系列才可以建全文索引。
不過MySQL從設計之初就是關係型資料庫,儲存引擎雖然支援全文檢索,整體架構上對全文檢索支援並不是太好而且限制很多,比如:1.每張表只能有一個全文檢索的索引,2.不支援沒有單詞界定符( delimiter)的語言,如中文、日語、韓語等的限制。
所以如果有大批量或者專門的全文檢索需求,還是應該選擇專業的全文檢索引擎,專業的人幹專業的事,畢竟Elastic靠著全文檢索起家,然後產品化、公司化並推出商業版本的解決方案然後融資上市,現在的市值已達100億美元(2021/12/06 -紐約證券交易所中的市值101.5億美元)。
從邏輯角度
主鍵索引
MySQL預設使用主鍵建立索引。
將索引和資料(或資料地址)一塊維護在一棵B+Tree上,樹的非葉子節點是主鍵,葉子節點是主鍵及主鍵對應的行記錄(或資料地址)。
MySQL預設使用主鍵建立索引樹,如果沒有主鍵會看是否有可以唯一標識一個行記錄的列,有則使用該列建立索引樹,沒有的話MySQL內部會建立一個隱含的列類似於rowid來建立索引樹。
非主鍵索引
除了主鍵外的其他欄位建立的索引,也叫普通索引,輔助索引、二級索引。
相較於主鍵索引,InnoDB儲存引擎的輔助索引會建立另外的一棵B+Tree,這棵索引樹的葉子節點的索引位(可以理解為key)就是我們選的索引列,葉子節點的資料位(可以理解為value)為主鍵值,而不是像主鍵索引那樣索引位是主鍵,資料位是那一行的完整資料(除主鍵外)。這麼做的目的一是為了節省空間,二是為了高效和保證資料的一致性。
從物理儲存角度
聚集索引(聚簇索引)
從物理儲存角度劃分,聚集索引把索引和行資料維護在一起,例如Innodb的主鍵索引。
聚集索引的葉子節點就是索引列+資料值,整個磁碟資料頁都是葉子節點。換句話說,資料頁上存放的是完整的每行記錄。因此聚集索引的一個優點就是:通過過聚集索引能獲取完整的整行資料。另一個優點是:對於主鍵的排序查詢和範圍查詢速度非常快。
非聚集索引(非聚簇索引)
這種索引相比較於聚集索引來說,是把索引和行資料分開維護,例如Innodb的非主鍵索引(輔助索引)。非聚集索引也會單獨維護一個索引樹(B+Tree結構),只不過它的葉子節點存的是索引列和主鍵值(InnoDB的輔助索引)或資料地址(MyISAM的索引)。
從列的個數角度
單列索引
顧名思義,被索引的列只有一列,例如create index idx_a on table_name(a)。
聯合索引(多列索引/複合索引)
也叫多列索引、複合索引,索引可以有多個列一起來建立,如create index idx_a_b on table_name(a,b),idx_a_b是索引名稱,索引列是a和b兩列。注意列順序不同建立的索引不同。
此時索引樹的組織方式大致如下圖,先按索引的第一列排序,再按索引的第二列排序。一個索引只會建立一個索引樹,即使是多列。
從唯一性角度
唯一索引
使用若干列建立唯一索引,create unique index u_idx_a_b on table_name(a,b),u_idx_a_b是索引名稱,則每一個行資料的這些列不能完全重複。當a、b相同時,第二條資料將不能插入或更新成功。當表中列有重複資料時,無法建立唯一索引。
例如有如下資料:
當執行INSERT INTO table_name(id, a, b) VALUES (3, '01', '0');會報錯:Duplicate entry '01-0' for key 'u_idx_a_b'
索引相關概念辨析
密集索引和稀疏索引
面試中還會被問到什麼是密集索引和稀疏索引。
密集索引:葉子節點儲存的不只是鍵值,還儲存了位於同一行記錄裡的其他列的資訊,由於密集索引決定了表的物理排列順序,一個表只有一個物理排列順序,所以一個表只能建立一個密集索引。
稀疏索引:葉子節點僅儲存了鍵位資訊以及該行資料的地址,有的稀疏索引只儲存了鍵位資訊及其主鍵。
mysiam儲存引擎,不管是主鍵索引,唯一鍵索引還是普通索引都是稀疏索引,innodb儲存引擎:有且只有一個密集索引。
所以,密集索引就是innodb儲存引擎裡的聚簇索引,稀疏索引就是innodb儲存引擎裡的普通二級索引。
回表
前面我們提到聚集索引是把索引和資料一塊維護在B+Tree中,當然我們也可以建立輔助索引(非聚集索引),而輔助索引的存在並不影響資料在聚集索引中的組織,且每張表上可以有多個輔助索引。
當通過輔助索引來查詢資料時,InnoDB儲存引擎會遍歷輔助索引並通過葉子節點獲得指向主鍵索引的主鍵,然後再通過主鍵索引(聚集索引)來找到一個完整的行記錄。這個過程被稱為回表。也就是根據輔助索引的值查詢一條完整的記錄需要使用到兩棵B+樹,即,一次輔助索引,一次聚集索引。
為什麼我們還需要一次回表操作呢?直接把完整的記錄放到輔助索引的葉子節點不就好了麼?剛才提到過,目的是節省空間和資料一致性。如果把完整的記錄放到葉子節點是可以不用回表,但是太佔地方了,相當於每建立一棵B+樹都需要把所有的記錄再都拷貝一遍,這就有點太浪費儲存空間了。而且每次對資料的變化要在所有包含資料的索引中全部都修改一次,為了保證資料的一致性,避免不了要做很多事務性操作,效能將非常低下。
所以,使用輔助索引查詢資料,且查詢的列不在索引樹中時,就需要回表了。很明顯,回表的記錄越少,效能提升就越高,需要回表的記錄越多,使用輔助索引的效能就越低,甚至讓某些查詢寧願使用全表掃描也不使用輔助索引。
那什麼時候採用全表掃描的方式,什麼時候使用採用輔助索引 + 回表的方式去執行查詢呢?這個就是查詢優化器做的工作,查詢優化器會事先對錶中的記錄計算一些統計資料,然後再利用這些統計資料根據查詢的條件來計算一下需要回表的記錄數,需要回表的記錄數越多,就越傾向於使用全表掃描,反之傾向於使用輔助索引 + 回表的方式。具體怎麼算的,不是今天的主題以後再說。
覆蓋索引/索引覆蓋
是一種索引優化方式而不是索引型別,是指查詢的列只需要通過索引樹就能查到,不需要二次回表。
InnoDB儲存引擎支援覆蓋索引(covering index,或稱索引覆蓋),即從輔助索引中就可以得到查詢的記錄,而不需要查詢聚集索引中的記錄。使用覆蓋索引的一個好處是輔助索引不包含整行記錄的所有資訊,故其大小要遠小於聚集索引,因此可以減少大量的IO操作。所以記住,覆蓋索引可以視為索引優化的一種方式,而並不是索引型別的一種。
除了覆蓋索引這個概念外,在索引優化的範圍內,還有字首索引等一系列概念。
還拿這個圖舉例,a和b兩列建立了聯合索引,當查詢select b from table where a = '02’就採用了覆蓋索引
字首索引
有時候需要索引很長的字元列,這會讓索引變得大且慢。可以索引開始的部分字元,這樣可以大大節約索引空間,從而提高索引效率。但這樣也會降低索引的選擇性。一般情況下我們需要保證某個列字首的選擇性也是足夠高的,以滿足查詢效能。(尤其對於BLOB、TEXT或者很長的VARCHAR型別的列,應該使用字首索引,因為MySQL不允許索引這些列的完整長度)。
訣竅在於要選擇足夠長的字首以保證較高的選擇性,同時又不能太長(以便節約空間)。字首應該足夠長,以使得字首索引的選擇性接近於索引整個列。
按照《阿里最新Java程式設計規範泰山版》中《(二) 索引規約》中的說法:
建議字首的長度為20區分度高達90%,我認為這不是絕對的,因為通過計算得出的資料才會更具說服力。
那在實際工作中具體如何做呢?
可以這樣計算區分度:區分度 = count(distinct left(列名, 索引長度))/count(*)
SELECT COUNT(DISTINCT LEFT(field, len))/COUNT(*) FROM table_name;
通過調整len的大小,計算獲得不同的區分度,區分度越大,越好。當隨著len的增加,區分度的值增加的緩慢的時就可以考慮選擇一個合適的len建立字首索引:
ALTER TABLE table_name ADD KEY (field(14));
建立字首索引後查詢語句並不需要更改:
select * from table_name where a = 'xxxx' ;
字首索引是一種能使索引更小、更快的有效辦法,但另一方面也有其缺點,那就是MySQL無法使用字首索引做ORDER BY和GROUP BY,也無法使用字首索引做覆蓋掃描。
有時候字尾索引 (suffix index)也很有用(例如,找到某個域名的所有電子郵件地址)。MySQL原生並不支援反向索引,但是我們可以轉換個思路,既然資料庫不支援,那我們可以利用一些小技巧讓它支援,比如把字串反轉後儲存,並基於此建立字首索引。還可以通過觸發器或者應用程式自行處理來維護索引。
好了,今天的分享就到這裡啦,如果有疑問或不對的地方,歡迎大家留言探討!
如果本篇文章對你有幫助,記得三連~