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