我覺得本身對索引很瞭解,直到我遇到了阿里面試官

點贊再看,養成習慣,微信搜索【三太子敖丙】我全部文章都在這裏,本文 GitHub github.com/JavaFamily 已收錄,有一線大廠面試完整考點,文末有福利java

前言

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

不知道你們是否是跟我想得同樣,我最想寫的是索引,爲啥呢?github

如下這個面試場景,不知道你們熟悉不熟悉:面試

面試官:數據庫有幾千萬的數據,查詢又很慢咱們怎麼辦?算法

面試者:加索引。sql

面試官:那索引有哪些數據類型?索引是怎麼樣的一種結構?哪些字段又適合索引呢?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天沒和人這樣說過話了,太開心了,不過不能開空調還得開窗戶通風,真的是超級超級冷。

這熟悉的工位,這熟悉的顯示器,個人眼角又……

白嫖很差,創做不易,各位的點贊就是丙丙創做的最大動力,咱們下篇文章見!

持續更新,未完待續……


文章每週持續更新,能夠微信搜索「 三太子敖丙 」第一時間閱讀,回覆【資料】【面試】【簡歷】有我準備的一線大廠面試資料和簡歷模板,本文 GitHub github.com/JavaFamily 已經收錄,有大廠面試完整考點,歡迎Star。

你知道的越多,你不知道的越多

相關文章
相關標籤/搜索