【MySQL數據庫】數據庫必學——關於MySQL索引的基礎知識都在這!

學習MySQL都逃不過索引,正確的使用索引可以優化增刪改查等操作的效率。索引的知識不僅重要也很多,包括索引的模型,索引的存儲方式以及主鍵索引普通索引等知識,都是需要了解了,我總結了自己學習索引的知識供大家參考學習。

一、 索引的常見模型

學習索引當然要先知道索引的底層是什麼數據結構實現的,也就是索引模型。
首先來介紹三種常見的數據結構:hash、有序數組、搜索樹

1.hash

hash相信大家其實已經不陌生了,我這裏簡單說一下:hash是以Key-value 形式的鍵值對存儲的,每一個數據在存儲時都會映射成一個key 存儲在對應的位置,如果發生衝突,就在每個Key下綴一個鏈表來存儲。只要輸入對應的Key值就能得到存儲的Value值,所以hash的查詢複雜度爲O(1),😮O(1)誒!很好的一個時間複雜度了,但是MySQL的索引卻不是以hash形式實現的…
原因是hash中存儲的元素都是無序的,而在數據庫中,經常會進行區間查詢,如果使用hash的方式只能進行全表掃描了😳,顯然,這就失去了索引的優化意義了。
所以hash索引比較適合等值查詢的情況!

2.有序數據

有序數組擁有很好的等值查詢和區間查詢的效率,在數組中由於數據有序存儲,這樣在搜索時可以使用二分查找時間複雜度爲O(logn),其實這也算一個很好的複雜度了,而且也滿足了區間查詢,但MySQL索引仍然不是使用的有序數組…
但看查詢效率確實很優秀,但如果進行插入和刪除呢?那不得把大量數據進行搬移嘛😓!這樣看有序數組也沒有很方便。
所以有序數組更加適合靜態存儲的情況!

3. 搜索樹

搜索樹🌳呢有一個自己獨有的特性,那就是對於這棵樹,左孩子永遠小於根節點,右孩子永遠大於根節點。其中我們常見的就是二叉搜索樹,對於二叉搜索樹而言,搜索的時間複雜度可以達到O(logn)。當然爲了一直保證是一顆二叉搜索樹,就要維持搜索樹成爲平衡樹,這樣使得更新的時間複雜度也爲O(logn)
搜索樹可以是二叉的,也可以是N叉的,這就構成了B樹,B樹是可以在一個節點中存儲多個數據,在節點內按照有序排列,也可以獲得多個孩子。對於MySQL的InnoDB引擎實現索引來說呢,這個N的值一般是1200。你可能知道,具體的底層不是由B樹構成的而是由 B+ 樹爲數據結構實現的。

首先我們要知道B+ 樹的特點:B+樹與B樹最不同的就是擁有兩個特性:

  • B+樹每個節點的數據都會在子節點中出現,所以最終的葉子節點是存有整棵樹所有的信息,而對一個數據的搜索也只能從從根節點一直走到葉子節點才能取到值。
  • B+ 樹的葉子節點之間按照鏈表的方式連接在一次,方便了區域查詢。

這裏找了張B+樹的圖供大家參考:
在這裏插入圖片描述

好現在來解決這個問題(當然建議先搞明白B樹和B+樹的結構):
那爲什麼有了B樹卻還要使用B+樹呢?

  1. IO次數減少
    在B+樹中,除了葉子節點是保存數據的,其它節點只是一個索引,沒有數據關聯,這就使得同一個磁盤頁中,可以存放更多的索引,也就是整個樹會變得矮胖,也就減少了磁盤IO的數目
  2. 查詢性能更加穩定
    由於B樹在查詢時,只要求找到該節點即可,可以樹中,也可以是葉子節點,這就使得查詢性能不穩定有着最好最壞一說。而對於B+樹由於數據存放在葉子節點,所以無論找哪一個數據都是一定要遍歷查找到葉子節點,也就是查詢複雜度始終爲樹的高度級別
  3. 查詢範圍更加簡便
    對於普通的B樹而言,要想對一個範圍查找,只能通過中序遍歷,這就導致要多次進行加載磁盤上的數據,而對於B+樹,只需要找到範圍下限,通過葉子節點存在着鏈表相連可以直接查找

以上就解答了爲什麼使用B+樹,也許你還有些模糊,讓我再來解釋解釋:
🚩爲什麼要減少磁盤IO?

CPU、內存、磁盤IO 各自工作效率有着很大的插別,其中進行磁盤IO時效率最慢,所以就要儘量減少IO的次數。

🚩爲什麼說樹變得矮胖,就減少了磁盤IO?

數據庫的數據都是保存到磁盤上的,使用時再加載到內存中。而數據在磁盤上又是以頁存儲的,對於B+索引樹,上面的每一個節點在磁盤上就是以一個數據頁的方式來存儲的。那麼想一想,是不是在尋找數據時每一層都需要使用一個節點索引,也就是要加載一個數據頁到內存,直到到達葉子節點。所以,樹越高,需要磁盤IO就越多,使用B+樹每個節點存放更多索引,這就很好的減少了樹的高度,也就減少了IO次數

🚩爲什麼範圍查找需要中序遍歷?

這是搜索樹的一個特性,對於一顆搜索樹,中序遍歷的結果就是樹的有序序列,對於B樹而言中序遍歷需要多次跨層服務,而B+樹由於葉子節點的設計就使得範圍查詢更加容易

好了,到這你大概對索引有了一個初步的認識,接着來看索引還有什麼其它規則吧!

二、主鍵索引和普通索引

學習了索引的底層實現,接着我們來認識下主鍵索引和普通索引

主鍵索引的葉子節點存的是整行數據。在InnoDB裏,主鍵索引也被稱爲聚簇索引(clustered index)
非主鍵索引的葉子節點內容是主鍵的值。在InnoDB裏,非主鍵索引也被稱爲二級索引(secondary index)

這裏需再囉嗦一下就是要分清楚主鍵索引和普通索引的存儲內容:主鍵索引的葉子節點存放的是整行數據,而普通索引的葉子節點存放的是對應的主鍵的值
所以如果我們使用的是主鍵索引搜索數據,那麼在遍歷到葉子節點的時候就可以直接取到該行的數據。而對於普通索引,在搜索到葉子節點時,找到的是對於數據的主鍵索引的值,此時再通過這個主鍵值再次到主鍵索引樹上搜索,直到找到主鍵索引樹上葉子節點中的行數據。這個過程稱之爲回表。所以我們通常要儘量使用主鍵索引避免產生回表,降低效率。

對於主鍵索引,我們可以選擇表中的一個字段作爲主鍵,也可以使用自增主鍵,自增主鍵就是每插入一行數據,索引的值都會有序加一,如果我們沒有指定主鍵索引,那麼MySQL會自動創建一個自增主鍵。那麼這就有了另一個問題:
什麼時候使用字段直接做主鍵索引,什麼時候又要使用自增主鍵做主鍵索引呢?

  1. 使用自增主鍵:我們已經知道,如果創建了二級索引,那麼二級索引的葉子節點存放的數據就是主鍵的ID,對於一個使用自增主鍵的表而言,自增主鍵佔用的大小往往要比業務字段佔用的大小更小。這樣在建立多個普通索引後, 使用自增主鍵大大減少了葉子節點存放佔用的內存。同時由於自增主鍵都是有序增加的,插入時不容易導致磁盤頁的分裂和合並。所以從性能和佔用內存方面看,自增主鍵往往更加合理。
  2. 使用業務字段做主鍵:當然,如果在業務邏輯中已經可以保證表中只有一個索引,這個索引也是一個唯一索引,那麼也就不用考慮普通索引帶來的存儲問題了,這時建議使用業務字段做索引,可以避免每次的回表。

你可能還有一些疑問,讓我再來解釋解釋:
🚩什麼是磁盤頁的合併以分裂?

B+樹爲了維護索引有序性,在插入新值的時候需要做必要的維護。當插入一個新值時,如果發現,該值位於一頁數據的中間,並且這一頁數據已經滿了,此時根據B+樹的算法,這時候需要申請一個新的數據頁,然後挪動部分數據過去。這個過程稱爲頁分裂。在這種情況下,性能自然會受影響。當然有分裂就有合併。當相鄰兩個頁由於刪除了數據,利用率很低之後,會將數據頁做合併。合併的過程,可以認爲是分裂過程的逆過程。

如果你已經清楚了,那就繼續往下看吧!

三、索引實現的優化

這一小節,我來講講關於使用索引時它實現的優化吧。

1.覆蓋索引

上面我們已經給大家提到了回表的概念,那是不是無論查找什麼都會回表呢?
其實也不是,如果普通索引中剛好包含了搜索語句中所需要的值,就會直接取得返回,而不會再次回表。
舉個例子吧:假設是一個student學生表,包含學生的學號、姓名、年齡、成績。這裏的主鍵索引是學號,而普通索引是姓名。
在這裏插入圖片描述
當執行selsct ID from student where name = "小蘭"時,使用的是姓名索引,當查找到葉子節點時,會發現葉子節點保存的就是需要的“小蘭”的ID,所以此時就會直接返回,而不會再次進行回表操作,這樣是不是就提升了效率呀👍!

由於覆蓋索引可以減少樹的搜索次數,顯著提升查詢性能,所以使用覆蓋索引是一個常用的性能優化手段。

2.最左前綴

到這裏你一定有一個疑問,如果爲每一種查詢都設計一個索引,索引是不是太多了,而如果不創建,那就只能全表掃描,也有點誇張。這裏就引入了最左前綴原則。
別急,我們先來認識另一種索引聯合索引,聯合索引就是多個字段同時做爲索引。這裏我們用(name,age)這個聯合索引來分析。這個索引可以通過上面講的覆蓋索引原則查詢name和age之間的關係。不僅如此,如果我們的SQL語句僅僅是查詢"where name like ‘小%’",此時就會使用最左前綴原則,用這個(name,age)索引來查到符合記錄的數據行。但是如果你想單獨查詢age字段,那就用不到這個索引了得新建一個age的單獨索引了,畢竟是最左前綴嘛👈,是根據索引的左邊匹配的。

知道了最左前綴原則後,那就自然有個問題:在建立聯合索引的時候,如何安排索引內的字段順序?
這裏我們的評估標準是,索引的複用能力。因爲可以支持最左前綴,所以當已經有了(a,b)這個聯合索引後,一般就不需要單獨在a上建立索引了。因此,第一原則是,如果通過調整順序,可以少維護一個索引,那麼這個順序往往就是需要優先考慮採用的。
那麼,如果既有聯合查詢,又有基於a、b各自的查詢呢?此時就要考慮空間的問題了。怎麼說呢?就是如果a字段佔用的內存比b字段大,那麼你覺得是(a,b)(b)呢 ?🆚還是(b,a)(a)呢? 當然是前者啦!額外建立的索引佔的空間更小當然就更好。

3. 索引下推

現在又有一個問題,對於上面的學生表,以(name,age)索引爲例,如果現在要檢索出表中“名字第一個字是小,而且年齡是18歲的成績大於90分的人”該怎麼做呢?

  • 首先使用這個索引,檢索出名字第一個字是小的所有人。
    在這裏插入圖片描述
  • 在MySQL 5.6之前呢,檢索出這三個人後,就要開始回表,在主鍵索引種找到行數據然後對比字段是否符合,因此要回三次表。看起來是不是笨笨的!🐷
  • 所以在5.6之後呢,就有了索引下推原則。查找到滿足條件的三個人後還會在這個聯合索引上繼續判斷age字段是不是滿足需求,不滿足就捨棄,滿足age的要求才會回表。也就是隻會回兩次表。這樣也就大大的優化了效率!

這一小節概念也比較多,

四、字符串索引

給普通字段加索引就是整個的字段都作爲索引,那給字符串字段加索引也是一定把整個字段都加上了索引嘛?這一小節,就來探討關於字符串索引的相關知識點吧!
對於字符串索引,如果不指定長度,會默認將整個字符串都作爲索引,但這樣就會佔用很大的內存空間,所以我們也可以通過指定字符串索引的長度來規定索引包含的字符串的長度。
現在有學生表,要在一個字符串類型的email字段上加索引,可以通過下面兩種方式:

alter table student add index index1(email);
alter table student add index index2(email(6)); 

第二種方式就是隻取了email的前六個字符作爲了索引。

兩種方式各有利弊:

  • 如果使用index1那麼顯然存儲空間會較大,但在查詢時,如果數據的前綴相似性很大,也可以精確搜索。
  • 對於index2,佔用空間是小,但是相應的,對於前綴相似性大的字段,它就會相應的多搜索幾條數據,也就會回表幾次。

前綴索引對覆蓋索引的影響
使用前綴索引還會影響到覆蓋索引,舉個例子:
select ID, email from student where email = '[email protected]';
此時你對比着看看使用index1和使用index2的區別,如果使用index1,是不是由於覆蓋索引的原則,就可以直接從普通索引獲取到數據而不需要回表,而對於index2就還要先根據email找出滿足條件的ID然後進行回表操作。這樣看來使用字符串的前綴索引也會帶來一些性能的降低。
這裏還要提一點,假設字段總長度爲10,那麼如果在定義索引時是明確指定10位的,也就是其實是把整個字符串都包含進去了,但是就像上面情況發生時,還是會回表😓因爲MySQL並不能確定已經完全包含了整個字段的信息。

其它方式的字符串索引:
既然說到前綴相似性大,那麼就有問題了,如果前綴有極大的相似性要怎麼辦呢?比如身份證號,又或者學校中學生的學號,每一種前綴都對應很多條數據,這時候有沒有什麼方法呢?

還是有的。

  • 第一種方式是使用倒序存儲。如果你存儲身份證號的時候把它倒過來存,每次查詢的時候,你可以這麼寫:
mysql> select field_list from t where id_card = reverse('input_id_card_string');

在每次輸入的身份證號碼進行反轉,然後查詢。由於身份證號碼的後面位數的區分度比前綴大,所以這種方式也是很可觀的。

  • 第二種方式是使用hash字段。你可以在表上再創建一個整數字段,來保存身份證的校驗碼,同時在這個字段上創建索引。

⏩介紹了兩種方法後,再看看它們之間的區別:

  1. 從佔用的額外空間來看,倒序存儲方式在主鍵索引上,不會消耗額外的存儲空間,而hash字段方法需要增加一個字段。當然,倒序存儲方式使用4個字節的前綴長度應該是不夠的,如果再長一點,這個消耗跟額外這個hash字段也差不多抵消了。

  2. 在CPU消耗方面,倒序方式每次寫和讀的時候,都需要額外調用一次reverse函數,而hash字段的方式需要額外調用一次crc32()函數。如果只從這兩個函數的計算複雜度來看的話,reverse函數額外消耗的CPU資源會更小些。

  3. 從查詢效率上看,使用hash字段方式的查詢性能相對更穩定一些。雖然有衝突的概率,但是概率非常小,可以認爲每次查詢的平均掃描行數接近1。而倒序存儲方式畢竟還是用的前綴索引的方式,也就是說還是會增加掃描行數。

總之使用前綴索引,定義好長度,就可以做到既節省空間,又不用額外增加太多的查詢成本。🔍

好了,這次的內容就是這些啦,其實也只是簡單的總結了一下自己的學習成果,很多細節還是應該慢慢逐漸的推敲。現在慢慢發現,學的越多,越能把很多知識串起來的感覺太棒了! 不過自己還是有很多的不足,文章如果有什麼問題也歡迎大家指正,希望自己表達的能對你有所幫助,也歡迎大家點贊關注一起進步!

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