你知道多少種索引?
在工作中我們常常用到索引,無論是普通索引還是唯一索引,都是一些常用的索引方式,目的就是為了提高查詢效率,避免業(yè)務(wù)請求超時等問題。那么,當你在使用索引的時候,是否思考過以下問題:索引是如何實現(xiàn)的,以及一共有哪些索引呢?
帶著這些問題,讓我們開始吧~
什么是索引
不知道你第一次聽到索引二字,是否會覺得它非常抽象,且難以理解。索引,什么是索引呢?
索引其實就是一種數(shù)據(jù)結(jié)構(gòu)。那么在計算機世界中,任何數(shù)據(jù)結(jié)構(gòu)都有它的用途,如棧、堆、樹、隊列等。那么,數(shù)據(jù)庫又什么要使用索引?為了提高查詢(檢索)效率的啦。所以,它就如同書本最前面的目錄一樣,作用就是作為檢索和排序,來極大程度提升你對數(shù)據(jù)的查詢速度。
所以,你可以理解索引是幫助存儲引擎高效獲取數(shù)據(jù)的一種方式,就如同你為了高效獲取書中的某一塊知識而去看書本目錄一樣。
索引的實現(xiàn)方式
索引通常使用的是叫做B樹(B Tree)和B+樹(B+ Tree)的數(shù)據(jù)結(jié)構(gòu)。也會使用哈希(Hash)作為索引,這幾種方式我們都會聊聊。
B樹
B Tree 指的就是 Balance Tree,也就是平衡樹。說說它的特點:
- 它是一顆多路查找樹,并且所有葉子節(jié)點位于同一層
- 每個節(jié)點中不僅包含數(shù)據(jù)的鍵值,還有data值
- 每個節(jié)點都相當于一個磁盤塊
你可以對它進行存儲數(shù)據(jù)、對其進行排序,由于是順序進行讀取、插入和刪除,所以它的時間復(fù)雜度為O(log n)。
B+樹
B+ Tree是基于B Tree實現(xiàn)的。它具有B Tree的平衡性,并且由于是有序的,也可以通過指針來進行順序訪問,時間復(fù)雜度依然為O(log n):
說說它的特點:
- 每個葉子節(jié)存儲索引字段的鍵值及字段對應(yīng)的數(shù)據(jù)
- 非葉子節(jié)點只存儲索引字段的鍵值及指向子節(jié)點的指針,不存儲數(shù)據(jù)
- 每個結(jié)點相當于一個磁盤塊
- 同一層級的葉子節(jié)點之間以雙向鏈表的形式相連
那么,為什么有了B樹,還需要B+樹呢?
其實,B樹和B+樹的最大區(qū)別就在于:B+樹的非葉子節(jié)點只會存儲指向下一個節(jié)點的地址,它并不存儲實際的值。并且它所有的葉子節(jié)點之間都是使用鏈表進行連接,由于是有序的,這樣進行遍歷查找會更快。
所以,B+樹就具備了B樹不具備的優(yōu)點。由于非葉子節(jié)點不存儲數(shù)據(jù),在同樣空間的內(nèi)存頁當中,就可以存放更多的key,這些key緊密、順序地排列,對于數(shù)據(jù)的查詢來說會比B樹更加穩(wěn)定和迅速,因為它能夠更好地利用空間局部性原理。
Hash
除了B樹和B+樹,還有一種索引就是哈希了。哈希索引就是基于哈希算法,對于每一行數(shù)據(jù),數(shù)據(jù)庫存儲引擎會對所有索引列都通過哈希算法去計算一個哈希碼,然后將這個哈希碼存儲在哈希索引中,由于使用的是哈希算法,所以使用哈希索引就會存在兩個弊端:
哈希算法計算出來的哈希值可能存在哈希沖突
由于計算出來的是個值所以無法進行范圍查詢
所以,B樹和B+樹是數(shù)據(jù)庫中比較常用到的兩種索引數(shù)據(jù)結(jié)構(gòu),而對于MySQL InnoDB存儲引擎來說,它使用的是B+樹。
索引的分類
我們剛剛講解的是索引的實現(xiàn)方式,那么對于索引還能將它以實際的應(yīng)用功能劃分和按照物理實現(xiàn)來進行劃分。
按照功能劃分
我們平時在寫SQL或者建表的時候,會根據(jù)實際的業(yè)務(wù)查詢頻率、特點和數(shù)據(jù)量為字段建立各種各樣的索引。那么這些索引可以根據(jù)實際應(yīng)用來進行分類:
- 主鍵(PRIMARY KEY)
- 唯一索引(UNIQUE)
- 普通索引(INDEX)
- 全文索引(FULLTEXT)
主鍵
在建表的時候只要指定了主鍵,就會生成對應(yīng)的索引。
為什么我這里說主鍵,而不說主鍵索引呢。很多人將主鍵和唯一索引、普通索引這些掛鉤在一起,稱之為“主鍵索引”。其實這是不準確的。因為主鍵本質(zhì)上來說,它是一種約束,而索引是一種數(shù)據(jù)結(jié)構(gòu),用來提升查詢效率。兩者不是一個層面的東西,也有著功能上質(zhì)的區(qū)別。
在這里我將主鍵根據(jù)功能劃分將它放進來,因為它是這些索引 、甚至是一張表的基礎(chǔ),對于MySQL的InnoDB存儲引擎來說,一個表結(jié)構(gòu)一定要有一個主鍵,表中的數(shù)據(jù)都是按照主鍵順序進行存放的。其它索引,也都要依賴于主鍵來生成索引。
主鍵的創(chuàng)建方式:
方式1:創(chuàng)建表的時候指定主鍵,如果創(chuàng)建表時沒有顯示定義主鍵,InnoDB就會通過以下規(guī)則去創(chuàng)建一個主鍵:
判斷表中是否存在一個非空的唯一索引。如果存在,則該索引對應(yīng)的字段名即為主鍵。如果不存在,則InnoDB會自動創(chuàng)建索引
方式2:通過SQL語句:
ALTER TABLE `table_name` ADD PRIMARY KEY `column_name`;
那們,我們常常說的“主鍵索引”又是怎么一回事呢?一會我們就會接觸到。
唯一索引
唯一索引就是作為一種索引,可以確保這行索引列不包含重復(fù)的值。所以它和非唯一索引的區(qū)別就是除了具有索引的功能,還限制這一字段在數(shù)據(jù)庫中不能有重復(fù)記錄,我們可以通過以下SQL創(chuàng)建唯一索引:
ALTER TABLE `table _name` ADD UNIQUE indexName;
普通索引
樸實無華,最普通的索引類型了。將一個或者多個字段添加索引,僅僅用來增加查詢速度。fancy在工作中創(chuàng)建最多的就是普通索引了。它的創(chuàng)建語句:
ALTER TABLE `table_name` ADD INDEX index_name;
全文索引
作為程序員,我們每日都要和搜索引擎打交道了。不僅僅是搜索引擎,在很多app上,都有一個全文搜索的功能:
如果不去使用一些中間件技術(shù),我們的每一次搜索,都要去根據(jù)關(guān)鍵詞去數(shù)據(jù)庫中查找。
如果不使用索引,那么在海量的數(shù)據(jù)里面要將你預(yù)期的結(jié)果篩選出來,可能要花費好幾秒的響應(yīng)時間。這對于用戶來說無疑是不可接受的。
所以,全文索引,就適合應(yīng)用于這樣的業(yè)務(wù)場景。
如果想要使用全文索引,就必須指定存儲引擎為MYISAM,因為InnoDB存儲引擎是不支持全文索引的。
由于全文索引存在許多弊端,比如不支持大小寫、索引創(chuàng)建慢等問題,所以不建議使用MySQL的全文索引。在現(xiàn)在的場景中,我們一般使用ElasticSearch這種封裝好的中間件來滿足全局數(shù)據(jù)檢索的需求。全文索引創(chuàng)建的方式:
ALTER TABLE `table_name` ADD FULLTEXT `column`;
按照索引數(shù)量劃分
如果一個索引加在一個字段上,那么它就是一個單列索引。但是我們也可以將多個字段聯(lián)合起來一起創(chuàng)建一個索引,這種索引就被稱為“聯(lián)合索引”,也叫多列索引或者組合索引。為了方便起見。我們之后都統(tǒng)一叫做“聯(lián)合索引”。
注:多個單列索引并不能被稱為組合索引
之后,我們會單獨地詳細講講這個聯(lián)合索引,以及它產(chǎn)生的一些問題,所以本文先不過多描述。
按照物理實現(xiàn)劃分
為了幫助大家更好理解,我們現(xiàn)在創(chuàng)建一張表:
create table fancyFamily
(
id int(11) not null auto_increment comment '家庭成員ID',
name varchar(16) not null comment '家庭成員名字',
age int(11) default null comment '家庭成員年齡',
primary key (id)
) engine InnoDB
AUTO_INCREMENT = 10
default charset = utf8;
然后插入一定的數(shù)據(jù)量:
insert into fancyFamily(id, name, age) values (1, 'fancy', 26);
insert into fancyFamily(id, name, age) values (3, '小黃', 25);
insert into fancyFamily(id, name, age) values (8, '大粉', 50);
insert into fancyFamily(id, name, age) values (10, '大黃', 55);
insert into fancyFamily(id, name, age) values (13, '小藍', 18);
insert into fancyFamily(id, name, age) values (16, '小白', 15);
聚集索引
什么是聚集索引?我們剛剛說過,對于一顆以B+樹為數(shù)據(jù)結(jié)構(gòu)創(chuàng)建的索引,只有葉子節(jié)點中存放數(shù)據(jù),并且葉子節(jié)點中的數(shù)據(jù)都是按照數(shù)據(jù)庫表中的數(shù)據(jù)一行一行進行連續(xù)排列的:
我們就將一顆滿足于以上數(shù)據(jù)存儲形式的索引形式,稱之為“聚集索引”。這里注重的是數(shù)據(jù)的存儲形式,也就是將數(shù)據(jù)存儲在葉子節(jié)點中。
并且由于一張表中的數(shù)據(jù)無法存放于兩顆不同的B+樹中。所以一張表也只能有一個聚集索引。
那我們現(xiàn)在就存在一個問題了:一張表中只能有一個主鍵,也只能有一個聚集索引,并且聚集索引還是按照B+樹中主鍵的數(shù)據(jù)存放形式來生成的,那么聚集索引不就是主鍵嗎?
還是上文提到的知識點:主鍵是一種約束,用來完善表結(jié)構(gòu)的數(shù)據(jù)完整性。而聚集索引是一種索引,目的就是將數(shù)據(jù)建立成有序的以便于減少查詢的時間復(fù)雜度。它是一種索引,而索引的用途就是用來提升查詢效率的。就像是你買房和買車一樣,它們之間的用途就有著本質(zhì)的區(qū)別。
那“主鍵索引”這個詞,又是怎么一回事呢?在InnoDB存儲引擎中,一張表一定會存在一個主鍵,它是索引能夠被用來提升效率的基礎(chǔ),只有擁有主鍵才能進行排序和查詢。而只有建立了主鍵,才能夠生成聚集索引這種數(shù)據(jù)存儲形式,所以當一張表的主鍵生成式,其對應(yīng)的聚集索引也便生成了。所以有人也喜歡將聚集索引稱為“主鍵索引”。
輔助索引
有了聚集索引,就會有非聚集索引。除了聚集索引之外的索引都叫做非聚集索引,比如以某非主鍵的字段創(chuàng)建索引后構(gòu)建起來的索引樹。它也叫二級索引或者輔助索引,為了方便起見,我們之后都將其稱為輔助索引。
那么它和聚集索引的區(qū)別是什么呢?最大的區(qū)別就是聚集索引的葉子結(jié)點是包含表結(jié)構(gòu)的全部行數(shù)據(jù)的,但是輔助索引并不包含。
它依然使用B+樹,只是每個葉子節(jié)點存儲的是聚集索引所在列的主鍵值:
不同于聚集索引,由于其不按照主鍵進行排列檢索,所以一張表中可以存在多個主鍵索引。
那么,如果我們現(xiàn)在創(chuàng)建了name這個輔助索引,并且去查詢小黃這個名字,它會怎么操作呢?
select * from workers where name='小黃';
它的查詢步驟如下:
- 通過name=’小黃‘這個條件去輔助索引找到對應(yīng)的葉子節(jié)點的鍵值
- 找到'小黃'對應(yīng)的數(shù)據(jù),也就是小黃這行數(shù)據(jù)對應(yīng)的主鍵
- 通過這個主鍵,返回聚集索引
- 通過聚集索引的主鍵排序,去葉子節(jié)點找到主鍵ID為3的小黃對應(yīng)的數(shù)據(jù)
也就是說,如果使用輔助索引,它不會像主鍵索引那樣直接去葉子節(jié)點找對應(yīng)的數(shù)據(jù),而是先去葉子節(jié)點找到對應(yīng)條件的主鍵,再返回聚集索引根據(jù)這個主鍵去搜索一遍。這種情況我們稱為回表:
也就是說,如果使用了輔助索引,是要比聚集索引多一次樹的訪問和遍歷的。
總結(jié)
以上我們描述了什么是索引、索引的實現(xiàn)方式、和索引的具體分類。通過索引的功能、數(shù)量、和物理實現(xiàn)可以區(qū)分為不同的索引,也具體描述了聚集索引和非聚集索引的區(qū)別。
在我身邊有工作了很多年的同事,他們業(yè)務(wù)能力有的一級棒,但是在講到數(shù)據(jù)庫的索引也會出現(xiàn)不夠清晰的劃分和總結(jié),所以我覺得將這些基礎(chǔ)知識理清十分重要。