MySQL深入學習(5)——索引

在學習索引之前,必須要先去了解B+樹的數據結構以及其相關算法知識,本文中不做詳細講解。可以參考我的另一篇文章y有關於B與B+樹的介紹

1. InnoDB存儲引擎索引類型

剛開始接觸MySQL中的索引時,真的很迷,因爲其索引分類真的搞不清,簡單列一下索引分類名詞:

  1. B+樹索引
  2. 全文索引
  3. 哈希索引
  4. 主鍵索引
  5. 唯一索引
  6. 普通索引
  7. 組合索引
  8. 聚集索引(聚簇索引)
  9. 非聚集索引(非聚簇索引)
  10. 主鍵索引(後來才知道其實主鍵索引就是聚簇索引)
  11. 輔助索引(輔助索引就是非聚集索引)

反正當時看到這些索引分類名詞是真的很暈,一個個看完才知道到底是怎麼分類的,以及各類索引的用途區別。

1.1 按照索引組織結構進行的分類(B+樹索引、全文索引、哈希索引)

    1.1.1 B+樹索引

    最常見的索引,目前的關係型數據庫基本都以B+樹的數據結構來管理組織索引和數據,爲什麼採用B+樹?首先要知道一點,數據庫讀取頁中的數據時並不是一行一行的讀取,而是直接掃描整個數據頁的數據放到內存中。而對於B+樹來說,其非葉子結點是不存儲數據的,只存儲索引,那麼這樣就能保證在一個數據頁的容量之內可以儘可能多的保存索引數據,這樣就可以快速的通過在一個索引頁中定位到數據的具體範圍(在哪個頁中),進而減少IO次數。

    B+樹結構的特點如下:

  • B+Tree中的非葉子結點不存儲數據,只存儲鍵值;
  • B+Tree的葉子結點沒有指針,所有鍵值都會出現在葉子結點上,且key存儲的鍵值對應data數據的物理地址;
  • B+Tree的每個非葉子節點由n個鍵值key和n個指針point組成;

這些特點就使得B+樹非常適合數據庫作爲數據庫索引結構,其具有磁盤讀寫代價低、查詢速度穩定的優點。

 

爲什麼B+ 樹的查詢性能優秀,磁盤讀寫代價低?

    因爲存儲引擎的設計專家巧妙的利用了外存(磁盤)的存儲結構,即磁盤的最小存儲單位是扇區(sector),而操作系統的塊(block)通常是整數倍的sector,操作系統以頁(page)爲單位管理內存,一頁(page)通常默認爲4K,數據庫的頁通常設置爲操作系統頁的整數倍,因此索引結構的節點被設計爲一個頁的大小,然後利用外存的“預讀取”原則,每次讀取的時候,把整個節點的數據讀取到內存中,然後在內存中查找,已知內存的讀取速度是外存讀取I/O速度的幾百倍,那麼提升查找速度的關鍵就在於儘可能少的磁盤I/O,那麼可以知道,每個節點中的key個數越多,那麼樹的高度越小,需要I/O的次數越少,因此一般來說B+Tree性能高,因爲B+Tree的非葉節點中不存儲data,就可以存儲更多的key。

    個人理解的話有一個非常形象的比喻來幫助理解爲什麼B+樹的結構能夠快速定位到數據頁:

(1)其實索引頁就是書中的目錄頁,書中目錄那幾頁其實就是數據庫中的索引頁, 而索引頁後面的內容就是數據頁。

(2)我們每次看書中的一頁內容,其實就相當於數據庫每次讀取一整個數據頁的數據到內存中。讀目錄頁就是讀取索引頁的索引數據到內存中。

(3)如果要查找某個內容,我們肯定會先去目錄頁中查找這個內容所在的頁碼範圍。比如說在《MySQL技術內幕》這本書中,我想知道關於B+樹索引的知識內容,那麼我肯定先去目錄翻找。如果第一頁目錄找不到,那就翻到下一頁目錄去找。一頁紙的大小肯定是有限的,所以如果我們在一頁中能夠存儲較多的目錄索引,我們就不需要進行多次翻頁操作,以最少的翻頁(IO)次數獲取最多的索引範圍,進而就能快速定位到目標查詢內容的位置範圍。

(4)爲什麼B+樹的非葉子結點頁不存儲數據?很容易理解,如果目錄中每個標題的內容都緊跟其後,這叫目錄嗎?在數據庫裏以這樣的方式存儲數據一個索引頁能放幾個索引?放在現實中的一本書裏,如果這樣幹,其實就相當於沒有目錄頁了,翻開書就是正文,如果你想看找某個部分的內容,只有一頁一頁的翻找了,效率可想而知。

   其實,這一塊用一個專業名詞來說,叫做扇入和扇出。

    1.1.2 全文索引

    FULLTEXT(全文)索引,僅可用於MyISAM和InnoDB,針對較大的數據,生成全文索引非常的消耗時間和空間。對於文本的大對象,或者較大的CHAR類型的數據,如果使用普通索引(B+樹索引),那麼匹配文本前幾個字符還是可行的(LIKE  word%),但是想要匹配文本中間的幾個單詞,那麼就要使用LIKE %word%來匹配,這樣需要很長的時間來處理,響應時間會大大增加,這種情況,就可使用時FULLTEXT索引了,在生成FULLTEXT索引時,會爲文本生成一份單詞的清單,在索引時及根據這個單詞的清單來索引。FULLTEXT可以在創建表的時候創建,也可以在需要的時候用ALTER或者CREATE INDEX來添加:

#創建表的時候添加FULLTEXT索引
CTREATE TABLE my_table(
    id INT(10) PRIMARY KEY,
    name VARCHAR(10) NOT NULL,
    my_text TEXT,
    FULLTEXT(my_text)
)ENGINE=MyISAM DEFAULT CHARSET=utf8;
#創建表以後,在需要的時候添加FULLTEXT索引
ALTER TABLE my_table ADD FULLTEXT INDEX (column_name);

全文索引的查詢也有自己特殊的語法,而不能使用LIKE %查詢字符串%的模糊查詢語法

SELECT * FROM table_name MATCH(ft_index) AGAINST('查詢字符串');


注意:

*對於較大的數據集,把數據添加到一個沒有FULLTEXT索引的表,然後添加FULLTEXT索引的速度比把數據添加到一個已經有FULLTEXT索引的錶快。

*5.6版本前的MySQL自帶的全文索引只能用於MyISAM存儲引擎,如果是其它數據引擎,那麼全文索引不會生效。5.6版本之後InnoDB存儲引擎開始支持全文索引

*在MySQL中,全文索引支隊英文有用,目前對中文還不支持。5.7版本之後通過使用ngram插件開始支持中文。

*在MySQL中,如果檢索的字符串太短則無法檢索得到預期的結果,檢索的字符串長度至少爲4字節,此外,如果檢索的字符包括停止詞,那麼停止詞會被忽略。

實際上,在真正的項目開發中,對於這種模糊匹配查詢,並不會使用MySQL這種關係型數據庫,所以全文索引用的極少,一般都會使用solr、elasticsearch等中間件實現,查詢性能極高。

    1.1.3 哈希索引

    在InnoDB存儲引擎中是不支持手動創建或者說干預哈希索引的創建的,因爲哈希索引是由InnoDB存儲引擎內部進行自動管理的,所以說官方文檔給出InnoDB的不支持哈希索引也沒問題,以爲InnoDB存儲引擎內部自動維護管理的,無法人爲干預是否在表中生成哈希索引。

    對於哈希索引的理解其實很簡單,其實與Java中的HashMap原理是一樣的,哈希索引用索引列的值計算該值的hashCode,然後在hashCode相應的位置存執該值所在行數據的物理位置,因爲使用散列算法,因此訪問速度非常快,但是一個值只能對應一個hashCode,而且是散列的分佈方式,因此哈希索引不支持範圍查找和排序。

1.2 按照索引字段進行的分類(主鍵索引、唯一索引、組合索引、普通索引)

注意一點,這幾個索引都是B+樹索引,但只有主鍵索引是聚集索引,其餘都是非聚集索引。

    1.2.1 主鍵索引

    即依據所指定的主鍵自動建立的索引,可以通過直接在建表的時候指定主鍵,也可以在建表之後再指定,但注意,主鍵索引只能存在一個,不能有多個主鍵索引(並不是說只能指定一個列作爲主鍵索引,主鍵索引可以作是組合索引);主鍵索引的添加刪除語句命令如下:(其實語法形式比較多,這裏不做全部說明,簡單列舉一種即可)

#創建表的時候指定主鍵
CREATE TABLE test  (
  id int(0) NOT NULL,
  name varchar(255),
  age int(0),
  PRIMARY KEY (`id`)
);

#添加主鍵索引
ALTER TABLE test
ADD PRIMARY KEY (`id`);

#刪除主鍵索引
ALTER TABLE test
DROP PRIMARY KEY;

    1.2.2 唯一索引

即要求索引字段在表中值唯一,相當於添加了一個唯一約束,但是值可以爲NULL。

方式1:ALTER TABLE `table_name` ADD UNIQUE  [indexName] (`column`)

比如:ALTER TABLE users ADD UNIQUE ( id )

方式2:CREATE UNIQUE INDEX index_name ON table_name (column_name)

比如:CREATE UNIQUE INDEX index_users ON users(id)

創建表的時候直接指定:CREATE TABLE tableName ( [...], UNIQUE [indexName] (tableColumns));

    1.2.3 組合索引

組合索引,即指的是由多列字段組成的索引,組合索引實際上包含主鍵索引、唯一索引、普通索引,因爲多列字段可以指定爲主鍵索引、唯一索引、普通索引。

ALTER TABLE 'table_name' ADD INDEX index_name('col1','col2','col3');
(普通索引)
或

create table 'table_name'(col1,col2,col3,col4,primary index col1_col2(col1,col2));

組合索引的結構以下表爲實例進行講解

CREATE TABLE People (
   last_name varchar(50)    not null,
   first_name varchar(50)    not null,
   dob        date           not null,
   gender     enum('m', 'f') not null,
   key(last_name, first_name, dob)
);

那麼對應的,其索引存儲結構應該如下圖所示

 

圖片描述
那麼在使用該聯合索引進行查找時,首先會依據第一個字段,也就是last_name的值進行查找,然後纔是first_name、dob,所以使用聯合索引時,必須遵從最左匹配原則才能通過聯合索引進行查找。其實另一種理解,聯合索引的建立就相當於建立了(last_name)/(last_name,first_name)/(last_name,first_name,dob)三個索引。

  在使用聯合索引時,where後面跟的過濾條件必須嚴格按照最左匹配原則,也就是說在where後面的過濾條件必須嚴格按照(last_name)/(last_name,first_name)/(last_name,first_name,dob)這三種順序,否則就無法使用聯合索引查詢。比如


會使用聯合索引查詢:
select * from People where last_name = '***';
select * from People where last_name = '***' and first_name = '**';
select * from People where last_name = '***' and first_name = '**' and dob = '**';

不會使用聯合索引的查詢:
select * from People where first_name = '**';
select * from People where first_name = '**' and dob = '**';

其實使用索引時還有另一種現象,叫做覆蓋索引,很多時候我們並不是要查詢獲取每行記錄中每個字段的數據,而是抽取部分字段的數據,而如果我們所抽取的部分字段恰好是索引的字段,那麼就會發生一個索引覆蓋現象。索引覆蓋會帶來一個好處,因爲B+樹中真正的全部數據都是放在葉子節點,而非葉子結點只會存放索引數據,當發生索引覆蓋時,就不會再向葉子節點進行查詢,直接將索引頁中的數據返回即可,這樣就會減少至少一次IO,對於性能的提升還是很明顯的,尤其是在聯合索引中,其實應用覆蓋索引原理的比較多。這也是爲什麼SQL語句都建議select具體字段,而不是無腦select *。

    1.2.4 普通索引

普通索引就很簡單了,就是指索引字段沒有任何的約束,值不唯一而且可以爲NULL。

1.3 按照數據存儲邏輯進行的分類(聚集索引與非聚集索引)

    首先,聚集索引又被稱爲聚簇索引和主索引,而非聚集索引又被稱爲非聚簇索引和輔助索引,這個要先明確。而且也都是B+樹索引。

    對於聚集索引和非聚集索引,我們首先明確一點:非聚集索引不存儲全部數據,聚集索引纔是真正存儲全部數據的地方。

聚集索引一般是表中的主鍵索引,如果表中沒有顯示指定主鍵,則會選擇表中的第一個不允許爲NULL的唯一索引,如果還是沒有的話,就採用Innodb存儲引擎爲每行數據內置的6字節ROWID作爲聚集索引。

 聚簇索引的主索引的葉子結點存儲的是鍵值對應的數據本身,輔助索引的葉子結點存儲的是鍵值對應的數據的主鍵鍵值。因此主鍵的值長度越小越好,類型越簡單越好。
聚簇索引的數據和主鍵索引存儲在一起。
聚簇索引的數據是根據主鍵的順序保存。因此適合按主鍵索引的區間查找,可以有更少的磁盤I/O,加快查詢速度。但是也是因爲這個原因,聚簇索引的插入順序最好按照主鍵單調的順序插入,否則會頻繁的引起頁分裂,嚴重影響性能。
在InnoDB中,如果只需要查找索引的列,就儘量不要加入其它的列,這樣會提高查詢效率。
 

*使用主索引的時候,更適合使用聚簇索引,因爲聚簇索引只需要查找一次,而非聚簇索引在查到數據的主鍵索引後,還要到聚集索引中進行查找數據。

*因爲聚簇輔助索引存儲的是主鍵的鍵值,因此可以在數據行移動或者頁分裂的時候降低成本,因爲這時不用維護輔助索引。但是由於主索引存儲的是數據本身,因此聚簇索引會佔用更多的空間。

*聚簇索引在插入新數據的時候比非聚簇索引慢很多,因爲插入新數據時需要檢測主鍵是否重複,這需要遍歷主索引的所有葉節點,而非聚簇索引的葉節點保存的是數據地址,佔用空間少,因此分佈集中,查詢的時候I/O更少,但聚簇索引的主索引中存儲的是數據本身,數據佔用空間大,分佈範圍更大,可能佔用好多的扇區,因此需要更多次I/O才能遍歷完畢。

 

從上圖中可以看到聚簇索引的輔助索引的葉子節點的data存儲的是主鍵的值,主索引的葉子節點的data存儲的是數據本身,也就是說數據和索引存儲在一起,並且索引查詢到的地方就是數據(data)本身,那麼索引的順序和數據本身的順序就是相同的;

而非聚簇索引的主索引和輔助索引的葉子節點的data都是目標數據的主鍵索引值,也就是說索引和數據並不是存儲在一起的,數據的順序和索引的順序並沒有任何關係,也就是索引順序與數據物理排列順序無關。

聚簇索引的解釋是:聚簇索引的順序就是數據的物理存儲順序

非聚簇索引的解釋是:索引順序與數據物理排列順序無關

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章