我以為我對索引非常瞭解,直到我遇到了阿里面試官...

敖丙發表於2020-06-29

前言

寫資料庫,我第一時間就想到了MySQL、Oracle、索引、儲存過程、查詢優化等等。

不知道大家是不是跟我想得一樣,我最想寫的是索引,為啥呢?

以下這個面試場景,不知道大家熟悉不熟悉:

面試官:資料庫有幾千萬的資料,查詢又很慢我們怎麼辦?

面試者:加索引。

面試官:那索引有哪些資料型別?索引是怎麼樣的一種結構?哪些欄位又適合索引呢?B+的優點?聚合索引和非聚合索引的區別?為什麼說索引會降低插入、刪除、修改等維護任務的速度?........

面試者:面試官怎麼出我們公司門來著?。

是的大家可能都知道慢了加索引,那為啥加,在什麼欄位上加,以及索引的資料結構特點,優點啥的都比較模糊或者甚至不知道。

那我們也不多BB了,直接開始這次的面試吧。

正文

我看你簡歷上寫到了熟悉MySQL資料庫以及索引的相關知識,我們就從索引開始,索引有哪些資料結構?

Hash、B+

大家去設計索引的時候,會發現索引型別是可以選擇的。

為什麼雜湊表、完全平衡二叉樹、B樹、B+樹都可以優化查詢,為何Mysql獨獨喜歡B+樹?

我先聊一下Hash:

大家可以先看一下下面的動圖

注意欄位值所對應的陣列下標是雜湊演算法隨機算出來的,所以可能出現雜湊衝突

那麼對於這樣一個索引結構,現在來執行下面的sql語句:

select * from sanguo where name='雞蛋'

可以直接對‘雞蛋’按雜湊演算法算出來一個陣列下標,然後可以直接從資料中取出資料並拿到所對應那一行資料的地址,進而查詢那一行資料, 那麼如果現在執行下面的sql語句:

select * from sanguo where name>'雞蛋'

則無能為力,因為雜湊表的特點就是可以快速的精確查詢,但是不支援範圍查詢

如果做成了索引,那速度也是很慢的,要全部掃描。

問個題外話,那Hash表在哪些場景比較適合?

等值查詢的場景,就只有KV(Key,Value)的情況,例如Redis、Memcached等這些NoSQL的中介軟體。

你說的是無序的Hash表,那有沒有有序的資料結構?

有序陣列,它就比較優秀了呀,它在等值查詢的和範圍查詢的時候都很Nice。

那它完全沒有缺點麼?

不是的,有序的適合靜態資料,因為如果我們新增、刪除、修改資料的時候就會改變他的結構。

比如你新增一個,那在你新增的位置後面所有的節點都會後移,成本很高。

那照你這麼說他根本就不優秀啊,特點也沒地方放。

此言差矣,可以用來做靜態儲存引擎啊,用來儲存靜態資料,例如你2019年的支付寶賬單,2019年的淘寶購物記錄等等都是很合適的,都是不會變動的歷史資料。

有點東西啊小夥子,那二叉樹呢?

二叉樹的新增和結構如圖:

二叉樹的結構我就不在這裡多BB了,不瞭解的朋友可以去看看資料結構章節。

二叉樹是有序的,所以是支援範圍查詢的。

但是他的時間複雜度是O(log(N)),為了維持這個時間複雜度,更新的時間複雜度也得是O(log(N)),那就得保持這棵樹是完全平衡二叉樹了。

怎麼聽你一說,平衡二叉樹用來做索引還不錯呢?

此言差矣,索引也不只是在記憶體裡面儲存的,還是要落盤持久化的,可以看到圖中才這麼一點資料,如果資料多了,樹高會很高,查詢的成本就會隨著樹高的增加而增加。

為了節約成本很多公司的磁碟還是採用的機械硬碟,這樣一次千萬級別的查詢差不多就要10秒了,這誰頂得住啊?

如果用B樹呢?

同理來看看B樹的結構:

可以發現同樣的元素,B樹的表示要比完全平衡二叉樹要“矮”,原因在於B樹中的一個節點可以儲存多個元素。

B樹其實就已經是一個不錯的資料結構,用來做索引效果還是不錯的。

那為啥沒用B樹,而用了B+樹?

一樣先看一下B加的結構:

我們可以發現同樣的元素,B+樹的表示要比B樹要“胖”,原因在於B+樹中的非葉子節點會冗餘一份在葉子節點中,並且葉子節點之間用指標相連。

那麼B+樹到底有什麼優勢呢?

其實很簡單,我們看一下上面的資料結構,最開始的Hash不支援範圍查詢,二叉樹樹高很高,只有B樹跟B+有的一比。

B樹一個節點可以儲存多個元素,相對於完全平衡二叉樹整體的樹高降低了,磁碟IO效率提高了。

而B+樹是B樹的升級版,只是把非葉子節點冗餘一下,這麼做的好處是為了提高範圍查詢的效率

提高了的原因也無非是會有指標指向下一個節點的葉子節點。

小結:到這裡可以總結出來,Mysql選用B+樹這種資料結構作為索引,可以提高查詢索引時的磁碟IO效率,並且可以提高範圍查詢的效率,並且B+樹裡的元素也是有序的。

那麼,一個B+樹的節點中到底存多少個元素最合適你有了解過麼?

額這個這個?臥*有點懵逼呀。

過了一會還是沒想出,只能老實交代:這個不是很瞭解咳咳。

你可以換個角度來思考B+樹中一個節點到底多大合適?

B+樹中一個節點為一頁或頁的倍數最為合適

為啥?

因為如果一個節點的大小小於1頁,那麼讀取這個節點的時候其實也會讀出1頁,造成資源的浪費。

如果一個節點的大小大於1頁,比如1.2頁,那麼讀取這個節點的時候會讀出2頁,也會造成資源的浪費。

所以為了不造成浪費,所以最後把一個節點的大小控制在1頁、2頁、3頁、4頁等倍數頁大小最為合適。

你提到了頁的概念,能跟我簡單說一下麼?

首先Mysql的基本儲存結構是(記錄都存在頁裡邊):

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

所以說,如果我們寫 select * from user where username='丙丙'這樣沒有進行任何優化的sql語句,預設會這樣做:

  • 定位到記錄所在的頁
    • 需要遍歷雙向連結串列,找到所在的頁
  • 從所在的頁內中查詢相應的記錄
    • 由於不是根據主鍵查詢,只能遍歷所在頁的單連結串列了

很明顯,在資料量很大的情況下這樣查詢會很慢!看起來跟回表有點點像。

哦?回表你聊一下。

臥槽,該死,我嘴幹嘛。

回表大概就是我們有個主鍵為ID的索引,和一個普通name欄位的索引,我們在普通欄位上搜尋:

select * from table where name = '丙丙'

執行的流程是先查詢到name索引上的“丙丙”,然後找到他的id是2,最後去主鍵索引,找到id為2對應的值。

回到主鍵索引樹搜尋的過程,就是回表。不過也有方法避免回表,那就是覆蓋索引

哦?那你再跟我聊一下覆蓋索引唄?

!!! 我這個嘴。。。

這個其實比較好理解,剛才我們是 select * ,查詢所有的,我們如果只查詢ID那,其實在Name欄位的索引上就已經有了,那就不需要回表了。

覆蓋索引可以減少樹的搜尋次數,提升效能,他也是我們在實際開發過程中經常用來優化查詢效率的手段。

很多聯合索引的建立,就是為了支援覆蓋索引,特定的業務能極大的提升效率。

索引的最左匹配原則知道麼?

最左匹配原則

  • 索引可以簡單如一個列 (a),也可以複雜如多個列 (a,b,c,d),即聯合索引
  • 如果是聯合索引,那麼key也由多個列組成,同時,索引只能用於查詢key是否存在(相等),遇到範圍查詢 (>、<、between、like左匹配)等就不能進一步匹配了,後續退化為線性查詢。
  • 因此,列的排列順序決定了可命中索引的列數

例子:

  • 如有索引 (a,b,c,d),查詢條件 a=1 and b=2 and c>3 and d=4,則會在每個節點依次命中a、b、c,無法命中d。(c已經是範圍查詢了,d肯定是排不了序了)

總結

索引在資料庫中是一個非常重要的知識點!

上面談的其實就是索引最基本的東西,N叉樹,跳錶、LSM我都沒講,同時要建立出好的索引要顧及到很多的方面:

  • 最左字首匹配原則。這是非常重要、非常重要、非常重要(重要的事情說三遍)的原則,MySQL會一直向右匹配直到遇到範圍查詢 (>,<,BETWEEN,LIKE)就停止匹配。
  • 儘量選擇區分度高的列作為索引,區分度的公式是 COUNT(DISTINCT col)/COUNT(*)。表示欄位不重複的比率,比率越大我們掃描的記錄數就越少。
  • 索引列不能參與計算,儘量保持列“乾淨”。比如, FROM_UNIXTIME(create_time)='2016-06-06' 就不能使用索引,原因很簡單,B+樹中儲存的都是資料表中的欄位值,但是進行檢索時,需要把所有元素都應用函式才能比較,顯然這樣的代價太大。所以語句要寫成 : create_time=UNIX_TIMESTAMP('2016-06-06')。
  • 儘可能的擴充套件索引,不要新建立索引。比如表中已經有了a的索引,現在要加(a,b)的索引,那麼只需要修改原來的索引即可。
  • 單個多列組合索引和多個單列索引的檢索查詢效果不同,因為在執行SQL時,MySQL只能使用一個索引,會從多個單列索引中選擇一個限制最為嚴格的索引(經指正,在MySQL5.0以後的版本中,有“合併索引”的策略,翻看了《高效能MySQL 第三版》,書作者認為:還是應該建立起比較好的索引,而不應該依賴於“合併索引”這麼一個策略)。
  • “合併索引”策略簡單來講,就是使用多個單列索引,然後將這些結果用“union或者and”來合併起來

思路文獻參考:

《MySQL實戰》

《高效能MySQL》

最後部分內容來自->java3y《索引和鎖》

丁奇《MySQL實戰》

絮叨

之前在B站傳了視訊:

大家反饋效果還是ok的,我後續會多多嘗試的,也希望把改進的建議留言反饋給我。

我去年拍攝了第一個超級粗糙的vlog:

因為拍攝剪輯手法都很垃圾,我就刪了,但是最近又想著放上去,在糾結哈哈,想看留個言我就傳了哈哈,我們下期間。

今天丙丙也開始了來杭16天后的第一次上班,很開心我們公司在杭州第一批覆工的名單中,我已經16天沒和人這樣說過話了,太開心了,不過不能開空調還得開窗戶通風,真的是超級超級冷。

這熟悉的工位,這熟悉的顯示器,我的眼角又......

白嫖不好,創作不易,各位的點贊就是丙丙創作的最大動力,我們下篇文章見!

持續更新,未完待續......

相關文章