MySQL(四):數據庫索引(最全索引面試詳解)

數據庫索引

 

一:索引基礎知識

 

什麼是索引?

索引是一種用於快速查詢和檢索數據的數據結構。常見的索引結構有: B樹, B+樹和Hash。

索引的作用就相當於目錄的作用。打個比方: 我們在查字典的時候,如果沒有目錄,那我們就只能一頁一頁的去找我們需要查的那個字,速度很慢。如果有目錄了,我們只需要先去目錄裏查找字的位置,然後直接翻到那一頁就行了。

 

爲什麼要用索引?索引的優缺點分析

索引的優點

可以大大加快 數據的檢索速度(大大減少的檢索的數據量), 這也是創建索引的最主要的原因。畢竟大部分系統的讀請求總是大於寫請求的。 *另外,通過創建唯一性索引,可以保證數據庫表中每一行數據的唯一性。

  1. 通過創建唯一性索引,可以保證數據庫表中每一行數據的唯一性。
  2. 可以大大加快 數據的檢索速度(大大減少的檢索的數據量), 這也是創建索引的最主要的原因。
  3. 幫助服務器避免排序和臨時表。
  4. 將隨機IO變爲順序IO
  5. 可以加速表和表之間的連接,特別是在實現數據的參考完整性方面特別有意義。

索引的缺點

  1. 創建索引和維護索引需要耗費許多時間:當對錶中的數據進行增刪改的時候,如果數據有索引,那麼索引也需要動態的修改,會降低SQL執行效率。
  2. 佔用物理存儲空間 :索引需要使用物理文件存儲,也會耗費一定空間。

 

使用索引的注意事項?

  1. 在經常需要搜索的列上,可以加快搜索的速度;
  2. 在經常使用在WHERE子句中的列上面創建索引,加快條件的判斷速度。
  3. 在經常需要排序的列上創 建索引,因爲索引已經排序,這樣查詢可以利用索引的排序,加快排序查詢時間;
  4. 對於中到大型表索引都是非常有效的,但是特大型表的話維護開銷會很大,不適合建索引
  5. 在經常用在連接的列上,這 些列主要是一些外鍵,可以加快連接的速度;
  6. 避免 where 子句中對宇段施加函數,這會造成無法命中索引。
  7. 在使用InnoDB時使用與業務無關的自增主鍵作爲主鍵,即使用邏輯主鍵,而不要使用業務主鍵。
  8. 將打算加索引的列設置爲 NOT NULL ,否則將導致引擎放棄使用索引而進行全表掃描
  9. 刪除長期未使用的索引,不用的索引的存在會造成不必要的性能損耗 MySQL 5.7 可以通過查詢 sys 庫的 chema_unused_indexes 視圖來查詢哪些索引從未被使用
  10. 在使用 limit offset 查詢緩慢時,可以藉助索引來提高性能

 

B樹和B+樹區別

  • B樹的所有節點既存放 鍵(key) 也存放 數據(data);

而B+樹只有葉子節點存放 key 和 data,其他內節點只存放key。

  • B樹的葉子節點都是獨立的;B+樹的葉子節點有一條引用鏈指向與它相鄰的葉子節點。
  • B樹的檢索的過程相當於對範圍內的每個節點的關鍵字做二分查找,可能還沒有到達葉子節點,檢索就結束了。而B+樹的檢索效率就很穩定了,任何查找都是從根節點到葉子節點的過程,葉子節點的順序檢索很明顯。


二:爲什麼索引能提高查詢速度

以下內容整理自: 地址: https://juejin.im/post/5b55b842f265da0f9e589e79 

先從 MySQL 的基本存儲結構說起

MySQL的基本存儲結構是頁(記錄都存在頁裏邊):

  • 各個數據頁可以組成一個雙向鏈表
  • 每個數據頁中的記錄又可以組成一個單向鏈表
  • 每個數據頁都會爲存儲在它裏邊兒的記錄生成一個頁目錄,在通過主鍵查找某條記錄的時候可以在頁目錄中使用二分法快速定位到對應的槽,然後再遍歷該槽對應分組中的記錄即可快速找到指定的記錄
  • 以其他列(非主鍵)作爲搜索條件:只能從最小記錄開始依次遍歷單鏈表中的每條記錄。

所以說,如果我們寫

select * from user where indexname = 'xxx'

這樣沒有進行任何優化的sql語句,默認會這樣做:

  1. 定位到記錄所在的頁:需要遍歷雙向鏈表,找到所在的頁
  2. 從所在的頁內中查找相應的記錄:由於不是根據主鍵查詢,只能遍歷所在頁的單鏈表了

很明顯,在數據量很大的情況下這樣查找會很慢!這樣的時間複雜度爲O(n)。

 

使用索引之後

索引做了些什麼可以讓我們查詢加快速度呢?其實就是將無序的數據變成有序(相對):

很明顯的是:沒有用索引我們是需要遍歷雙向鏈表來定位對應的頁,現在通過 “目錄” 就可以很快地定位到對應的頁上了!(二分查找,時間複雜度近似爲O(logn))其實底層結構就是B+樹,B+樹作爲樹的一種實現,能夠讓我們很快地查找出對應的記錄。

 

選擇索引和編寫利用這些索引的查詢的3個原則

 

  1. 單行訪問是很慢的。

特別是在機械硬盤存儲中(SSD的隨機I/O要快很多,不過這一點仍然成立)。如果服務器從存儲中讀取一個數據塊只是爲了獲取其中一行,那麼就浪費了很多工作。最好讀取的塊中能包含儘可能多所需要的行。使用索引可以創建位置引,用以提升效率。

  1. 按順序訪問範圍數據是很快的,這有兩個原因。

第一,順序1/0不需要多次磁盤尋道,所以比隨機I/O要快很多(特別是對機械硬盤)。

第二,如果服務器能夠按需要順序讀取數據,那麼就不再需要額外的排序操作,並且GROUPBY查詢也無須再做排序和將行按組進行聚合計算了。

  1. 索引覆蓋查詢是很快的。如果一個索引包含了查詢需要的所有列,那麼存儲引擎就 不需要再回表查找行。這避免了大量的單行訪問,而上面的第1點已經寫明單行訪 問是很慢的。

 

三:Mysql索引主要使用的兩種數據結構

 

哈希索引

對於哈希索引來說,底層的數據結構就是哈希表,因此在絕大多數需求爲單條記錄查詢的時候,可以選擇哈希索引,查詢性能最快;其餘大部分場景,建議選擇BTree索引。

Hash索引定位快

Hash索引指的就是Hash表,最大的優點就是能夠在很短的時間內,根據Hash函數定位到數據所在的位置,這是B+樹所不能比的。

Hash衝突問題

知道HashMap或HashTable的同學,相信都知道它們最大的缺點就是Hash衝突了。不過對於數據庫來說這還不算最大的缺點。

Hash索引不支持順序和範圍查詢(Hash索引不支持順序和範圍查詢是它最大的缺點)

BTree索引

MyISAM和InnoDB實現BTree索引方式的區別

MyISAM

B+Tree葉節點的data域存放的是數據記錄的地址。在索引檢索的時候,首先按照B+Tree搜索算法搜索索引,如果指定的Key存在,則取出其 data 域的值,然後以 data 域的值爲地址讀取相應的數據記錄。這被稱爲“非聚簇索引”。

InnoDB

其數據文件本身就是索引文件。相比MyISAM,索引文件和數據文件是分離的,其表數據文件本身就是按B+Tree組織的一個索引結構,樹的葉節點data域保存了完整的數據記錄。這個索引的key是數據表的主鍵,因此InnoDB表數據文件本身就是主索引。這被稱爲“聚簇索引(或聚集索引)”,而其餘的索引都作爲輔助索引,輔助索引的data域存儲相應記錄主鍵的值而不是地址,這也是和MyISAM不同的地方。在根據主索引搜索時,直接找到key所在的節點即可取出數據;在根據輔助索引查找時,則需要先取出主鍵的值,在走一遍主索引。 因此,在設計表的時候,不建議使用過長的字段作爲主鍵,也不建議使用非單調的字段作爲主鍵,這樣會造成主索引頻繁分裂。 PS:整理自《Java工程師修煉之道》

 

四:索引類型

 

主鍵索引(Primary Key)

數據表的主鍵列使用的就是主鍵索引。

一張數據表有只能有一個主鍵,並且主鍵不能爲null,不能重複。

在mysql的InnoDB的表中,當沒有顯示的指定表的主鍵時,InnoDB會自動先檢查表中是否有唯一索引的字段,如果有,則選擇該字段爲默認的主鍵,否則InnoDB將會自動創建一個6Byte的自增主鍵。

 

二級索引(輔助索引)

二級索引又稱爲輔助索引,是因爲二級索引的葉子節點存儲的數據是主鍵。也就是說,通過二級索引,可以定位主鍵的位置。

唯一索引,普通索引,前綴索引等索引屬於二級索引。

PS:不懂的同學可以暫存疑,慢慢往下看,後面會有答案的,也可以自行搜索。

  1. 唯一索引(Unique Key) :唯一索引也是一種約束。

唯一索引的屬性列不能出現重複的數據,但是允許數據爲NULL,一張表允許創建多個唯一索引。建立唯一索引的目的大部分時候都是爲了該屬性列的數據的唯一性,而不是爲了查詢效率。

  1. 普通索引(Index) 

普通索引的唯一作用就是爲了快速查詢數據,一張表允許創建多個普通索引,並允許數據重複和NULL。

  1. 前綴索引(Prefix) 

前綴索引只適用於字符串類型的數據。前綴索引是對文本的前幾個字符創建索引,相比普通索引建立的數據更小, 因爲只取前幾個字符。

  1. 全文索引(Full Text) 

全文索引主要是爲了檢索大文本數據中的關鍵字的信息,是目前搜索引擎數據庫使用的一種技術。Mysql5.6之前只有MYISAM引擎支持全文索引,5.6之後InnoDB也支持了全文索引。

 

五:聚集索引與非聚集索引

 

聚集索引

聚集索引即索引結構和數據一起存放的索引。主鍵索引屬於聚集索引。

在 Mysql 中,InnoDB引擎的表的 .ibd文件就包含了該表的索引和數據,對於 InnoDB 引擎表來說,該表的索引(B+樹)的每個非葉子節點存儲索引,葉子節點存儲索引和索引對應的數據。

 

聚集索引的優點

聚集索引的查詢速度非常的快,因爲整個B+樹本身就是一顆多叉平衡樹,葉子節點也都是有序的,定位到索引的節點,就相當於定位到了數據。

 

聚集索引的缺點

  1. 依賴於有序的數據 :因爲B+樹是多路平衡樹,如果索引的數據不是有序的,那麼就需要在插入時排序,如果數據是整型還好,否則類似於字符串或UUID這種又長又難比較的數據,插入或查找的速度肯定比較慢。
  2. 更新代價大 : 如果對索引列的數據被修改時,那麼對應的索引也將會被修改, 而且況聚集索引的葉子節點還存放着數據,修改代價肯定是較大的, 所以對於主鍵索引來說,主鍵一般都是不可被修改的。

 

非聚集索引

 

非聚集索引即索引結構和數據分開存放的索引。

 

二級索引屬於非聚集索引。

MYISAM引擎的表的.MYI文件包含了表的索引, 該表的索引(B+樹)的每個葉子非葉子節點存儲索引, 葉子節點存儲索引和索引對應數據的指針,指向.MYD文件的數據。

非聚集索引的葉子節點並不一定存放數據的指針, 因爲二級索引的葉子節點就存放的是主鍵,根據主鍵再回表查數據。

非聚集索引的優點

更新代價比聚集索引要小 。非聚集索引的更新代價就沒有聚集索引那麼大了,非聚集索引的葉子節點是不存放數據的

非聚集索引的缺點

  1. 跟聚集索引一樣,非聚集索引也依賴於有序的數據
  2. 可能會二次查詢(回表) :這應該是非聚集索引最大的缺點了。 當查到索引對應的指針或主鍵後,可能還需要根據指針或主鍵再到數據文件或表中查詢。

這是Mysql的表的文件截圖:

聚集索引和非聚集索引:(偷張圖)

 

 

非聚集索引一定回表查詢嗎(覆蓋索引)?

 

非聚集索引不一定回表查詢。

試想一種情況,用戶準備使用SQL查詢用戶名,而用戶名字段正好建立了索引。

SELECT name FROM table WHERE username='guang19';

那麼這個索引的key本身就是name,查到對應的name直接返回就行了,無需回表查詢。

即使是MYISAM也是這樣,雖然MYISAM的主鍵索引確實需要回表, 因爲它的主鍵索引的葉子節點存放的是指針。但是如果SQL查的就是主鍵呢?

SELECT id FROM table WHERE id=1;

主鍵索引本身的key就是主鍵,查到返回就行了。這種情況就稱之爲覆蓋索引了。

 

六:覆蓋索引

 

如果一個索引包含(或者說覆蓋)所有需要查詢的字段的值,我們就稱之爲“覆蓋索引”。我們知道在InnoDB存儲引擎中,如果不是主鍵索引,葉子節點存儲的是主鍵+列值。最終還是要“回表”,也就是要通過主鍵再查找一次。這樣就會比較慢覆蓋索引就是把要查詢出的列和索引是對應的,不做回表操作!

覆蓋索引即需要查詢的字段正好是索引的字段,那麼直接根據該索引,就可以查到數據了, 而無需回表查詢。

如主鍵索引,如果一條SQL需要查詢主鍵,那麼正好根據主鍵索引就可以查到主鍵。

再如普通索引,如果一條SQL需要查詢name,name字段正好有索引, 那麼直接根據這個索引就可以查到數據,也無需回表。

覆蓋索引: 

 

七:索引創建原則

 

單列索引

單列索引即由一列屬性組成的索引。

 

聯合索引(多列索引)

聯合索引即由多列屬性組成索引。

 

最左前綴原則

假設創建的聯合索引由三個字段組成:

ALTER TABLE table ADD INDEX index_name (num,name,age)

那麼當查詢的條件有爲:num / (num AND name) / (num AND name AND age)時,索引才生效。所以在創建聯合索引時,儘量把查詢最頻繁的那個字段作爲最左(第一個)字段。查詢的時候也儘量以這個字段爲第一條件。

但可能由於版本原因(我的mysql版本爲8.0.x),我創建的聯合索引,相當於在聯合索引的每個字段上都創建了相同的索引:

無論是否符合最左前綴原則,每個字段的索引都生效:

 

八:索引創建注意點

 

最左前綴原則

雖然我目前的Mysql版本較高,好像不遵守最左前綴原則,索引也會生效。 但是我們仍應遵守最左前綴原則,以免版本更迭帶來的麻煩。

選擇合適的字段

 

1.不爲NULL的字段

索引字段的數據應該儘量不爲NULL,因爲對於數據爲NULL的字段,數據庫較難優化。如果字段頻繁被查詢,但又避免不了爲NULL,建議使用0,1,true,false這樣語義較爲清晰的短值或短字符作爲替代。

2.被頻繁查詢的字段

我們創建索引的字段應該是查詢操作非常頻繁的字段。

 

3.被作爲條件查詢的字段

被作爲WHERE條件查詢的字段,應該被考慮建立索引。

 

4.被經常頻繁用於連接的字段

經常用於連接的字段可能是一些外鍵列,對於外鍵列並不一定要建立外鍵,只是說該列涉及到表與表的關係。對於頻繁被連接查詢的字段,可以考慮建立索引,提高多表連接查詢的效率。

 

九:不合適創建索引的字段

 

1.被頻繁更新的字段應該慎重建立索引

雖然索引能帶來查詢上的效率,但是維護索引的成本也是不小的。 如果一個字段不被經常查詢,反而被經常修改,那麼就更不應該在這種字段上建立索引了。

 

2.不被經常查詢的字段沒有必要建立索引

3.儘可能的考慮建立聯合索引而不是單列索引

因爲索引是需要佔用磁盤空間的,可以簡單理解爲每個索引都對應着一顆B+樹。如果一個表的字段過多,索引過多,那麼當這個表的數據達到一個體量後,索引佔用的空間也是很多的,且修改索引時,耗費的時間也是較多的。如果是聯合索引,多個字段在一個索引上,那麼將會節約很大磁盤空間,且修改數據的操作效率也會提升。

 

4.注意避免冗餘索引

冗餘索引指的是索引的功能相同,能夠命中 就肯定能命中 ,那麼 就是冗餘索引如(name,city )和(name )這兩個索引就是冗餘索引,能夠命中後者的查詢肯定是能夠命中前者的 在大多數情況下,都應該儘量擴展已有的索引而不是創建新索引。

 

5.考慮在字符串類型的字段上使用前綴索引代替普通索引

前綴索引僅限於字符串類型,較普通索引會佔用更小的空間,所以可以考慮使用前綴索引帶替普通索引。

 

還有兩章MySQL知識的基礎版就整理完了,從四月初開始重新寫博客,每日更新,努力將自己的知識串聯起來!很累,但是也很幸福,二次元死宅的我也只能在文章的結尾聊以慰藉,願所有的努力都有收穫;有一天我能對自己說,你努力的樣子很美!

你們的老婆來了!

 

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