小面試官教你 MySQL——引擎、索引和算法

MySQL 引擎、索引和算法

弄懂了 MySQL 的基本 CURD 操做以後,下一個必須掌握的知識就是 MySQL 的索引。html

我在面試中,常常喜歡針對 MySQL 的知識由淺入深地問下去,瞭解候選人對 MySQL 知識的瞭解到了哪個層級。上一篇文章中的那些知識太基礎了,我是不會拿來問的。所以我會問的第一個問題必然是 MySQL 的索引。mysql

關於 MySQL 的索引,我大體會問下面幾個問題:git

  1. 你知道 InnoDB 索引所使用的算法是什麼嗎?
  2. 爲何 InnoDB 要使用 B+ 樹而不是其餘的數據結構呢?
  3. 在 InnoDB 中,是否是必需要有主鍵?若是建表的時候不指定主鍵會怎樣?
  4. InnoDB 的主鍵和索引有什麼區別?

要回答這兩個問題,咱們須要瞭解下面幾個知識:引擎、索引、樹程序員

MySQL 索引的背景知識

MySQL 的引擎

MySQL 在設計之初,就容許嵌入不一樣的引擎。數據庫的核心算法其實是由引擎來實現的。早期 MySQL 數據庫有如下三個主流引擎:面試

  1. MyISAM: 這是 MySQL 5.5 以前的默認引擎。因爲其不支持事務處理,所以在新的系統中基本上沒什麼人用了。
  2. InnoDB: 這是 MySQL 5.6 以及以後的默認引擎。若是你不知道應該選什麼引擎的話,選它基本沒錯。
  3. Memory: 這是一個特殊的引擎,該引擎存取的數據,所有放在內存中,不會落入磁盤。所以當數據庫宕機或重啓後,數據就會丟失。自從 Redis 興起以後,memory 引擎也式微了。

因爲新系統中幾乎都選用了 InnoDB 引擎,所以下文中如無特別說明,則指的均爲 InnoDB 引擎下的軟件原理和行爲。算法

存儲系統中的 「頁」

按照參考資料1 InnoDB 引擎的關鍵特性包括如下內容:sql

  1. 插入緩衝(Insert Buffer)
  2. 兩次寫(Double Write)
  3. 自適應哈希索引(Adapitve Hash Index)
  4. 異步 IO(Async IO)
  5. 刷新臨接頁(Flush Neighbor Page)

能夠看到五個特性中,有四個特性是和存儲直接相關的。學過計算機組成原理的話就會知道,計算機存儲,根據其與 CPU 的距離由近到遠有如下幾個:數據庫

  1. 寄存器
  2. 緩存
  3. 內存
  4. 硬盤

其中寄存器對於程序員來講通常是不須要關注的;緩存沒法直接在程序中影響和操做。對於絕大部分的計算機程序所操做的存儲爲內存和硬盤。操做系統讀取內存和硬盤的時候,基本上以 「頁」 爲單位進行操做的。segmentfault

爲何須要以 「頁」 爲單位操做呢?我們先看內存:內存實際上是徹底能夠隨機讀取的,也就是說 CPU 若是想要讀取哪個地址上的數據,那麼一條指令就能夠取到。可是應用程序上存取的不是實際內存,而是虛擬內存。而操做系統映射虛擬內存,只能以頁爲單位進行映射。所以,即使你操做的是內存,仍是請自覺儘可能對齊頁。api

重點則是硬盤。硬盤包括兩種類型,一種是磁盤,也就是以磁性元件來存儲數據的介質;另外一種是所謂的 SSD,也就是固態硬盤。關於磁盤的讀取原理和過程,我以前寫過兩篇文章《高性能磁盤 I/O 開發學習筆記 -- 硬件原理篇》和《高性能磁盤 I/O 開發學習筆記 -- 軟件手段篇》。簡單而言,因爲硬件原理的限制,硬盤的讀寫有如下兩個特色:

  1. 慢: 磁盤須要轉才能轉到對應的位置;SSD 好不少,但也比不上內存,畢竟要從長長的總線加載到內存中呢
  2. 塊: 在軟件中使用的 page,在硬件屆常常是能夠對應到 block。磁盤和 SSD 的數據修改都是以 block 爲單位的

索引的原理

MySQL 定位的是大量數據的數據存儲。每個表中存儲的數據目標是百萬行起跳;數據數據結構較爲簡單,索引效率高的話,千萬也沒有問題。實際使用中,也有上億的場景。這就像一個圖書館,咱們須要對每一本書進行標記和索引,這樣在查找書目(數據)時,纔可以高效地查詢到所須要的數據。

索引的原理,本質上就是解決快速查找和快速修改的目的。其次則是解決很是糾結的硬盤寫入流程,整個過程當中還須要各類防止崩潰和宕機——畢竟 MySQL 的數據一致性要求是很高的。

做爲 MySQL,常常須要關注的數據結構有如下幾個:哈希表、B-樹、B+樹。

哈希表

哈希(hash)算法相信你們都瞭解了,本文就不贅述。哈希算法的時間複雜度爲 O(1)。在 MySQL 中,前文提到的三個主要引擎只有 Memory 引擎在索引中使用了哈希算法。那爲何其餘引擎不是用這個算法呢?由於其餘引擎須要考慮落地硬盤的問題啊。
哈希的算法雖然簡單,可是哈希表在實際應用中須要考慮表的擴容和縮容的問題。當哈希表須要擴容/縮容的時候,整個表中的全部元素均可能須要從新排列。Memory 引擎是不落磁盤的,不 care。但即使是 Memory,也不適合存儲大量數據。實際上在現實使用中,Memory 的使用場景已經不斷被壓縮,大部分已經被 Redis 所取代了。

B樹

B樹的原理其實相對而言比較簡單,它就是一棵樹。B樹相比起最基本的樹結構來講,比較特別的就是樹的分裂和合並。主要就是在數據庫的內容增長和減小的時候所發生。具體的過程讀者能夠查閱網上相關的資料,很是多。
B樹的特色是:

  • 每個節點能夠多路分叉,不是二叉樹。查詢效率上比起二叉樹而言確定是較弱的。
  • 在其每個節點上都會存儲數據

不是 MySQL 的 MongoDB 使用的就是 B樹。那麼這裏的問題是:爲何採用 B樹,而不是搜索效率更高的紅黑樹呢?(面試考點注意!)

  1. 首先,B樹每個節點是有必定長度的,在引擎的設計中,會充分利用這一特性,結合前文所提到的 「頁」,將同一個節點放在同一頁中,大大提升硬盤的存取效率
  2. 其次,首先,紅黑樹在插入的過程當中,常常會出現節點的旋轉,旋轉次數多了以後,可能致使附近的節點分散分佈在硬盤的多個頁中。那麼在數據落地的時候,就會大大下降效率,而且提升失敗的風險

須要注意的是,B樹有時候也被稱爲B-樹,可是有些文章中B樹指的又不是B-樹,而是二叉樹(Binary Tree)。讀者在識別這些用詞的時候,要結合上下文區分。

B+樹

B+樹是本文的重點,由於 InnoDB 使用的樹結構就是B+樹。一個B+樹的示意結構圖以下:

看起來和B樹是很像的,可是實際上有兩個很是關鍵的差別:

  1. B樹的數據不只存在葉子結點中,分支節點也存儲。可是B+樹的數據僅僅存儲在葉子結點中,分支節點僅保存索引。若是要查詢到數據,那麼必須查到葉子結點才能查到。
  2. B樹的各個節點之間除了父子關係以外,不會有其餘的關係。可是B+樹的葉子節點之間,還有雙向鏈表相互鏈接。這一點的好處是,對於設計遍歷操做,或者是 offset - limit 的操做,可以大大地提升搜索效率

InnoDB 索引的分類

前文提到,InnoDB 所使用的算法是B+樹;B+樹上的非葉子結點存儲的只是數據結構的索引,用於定位子結點用的,而不是 「數據庫索引」。

那麼問題來了:InnoDB B+ 樹的葉子結點保存的是什麼呢?這就引出了第一個分類:

按存儲內容區分

Clustered Index,中文翻譯不一,有 「聚簇索引」、「彙集索引」、「聚類索引」 等。聚簇索引指的是在葉子結點上,存儲的數據就是完整的 MySQL 的一行數據。

那麼在B+樹的內部,用什麼來索引葉子結點呢?答案是主鍵(main key)。在實際應用中,很大一部分的表在建立的時候都會把第一列定義爲 int 或者 bigint 類型,而且指定爲 auto increment類型並設定爲主鍵。這是一個很是通用並且很是保險的作法。咱們聯繫一下前文 B+ 樹的特性就能夠發現,若是針對這個自增ID直接進行查詢、或者是以自增ID爲條件進行大於、小於等範圍操做,都很是高效。

那麼若是在建表的時候不明確指定自增ID的話,會怎樣呢?B+樹失效?
對於 MyISAM 引擎來講,主鍵不是必須的,若是不指定主鍵,那就沒有主鍵。可是在 InnoDB 中主鍵是必要的,若是不指定主鍵的話,那麼 InnoDB 會隱含地添加一個 24 位寬的整型ID做爲主鍵。但這會致使這個整型 ID 不可見,致使相關的一些操做好比 last inserted id 變得沒有意義。所以在實際操做中咱們仍是須要顯式地指定主鍵。

對於 InnoDB 來講,聚簇索引能夠等同於就是主鍵的索引。

Secondary Index,中文翻譯也不一,有 「非聚簇索引」、「輔助索引」、「二級索引」 等。在非聚簇索引的葉子結點上,存儲的是對應的那一行 MySQL 數據的主鍵。

若是經過非聚簇索引,也就是除了主鍵之外的字段查找到了條目以後,此時 InnoDB 僅僅拿到了兩個數據:一個是當前節點的索引列的值;另外一個是主鍵。若是客戶端還請求了其餘數據的話,那麼 InnoDB 須要再到當前表的聚簇索引中進行查閱。這個動做稱爲 「回表」 查詢。

按組成邏輯區分

按照組成邏輯區分的話,InnoDB 索引能夠分爲:

  • 主鍵索引: 這就是前文提到的聚簇索引
  • 單列索引: 除了主鍵以外的非聚簇索引的最簡單的模式
  • 聯合索引: 顧名思義,就是多列的索引
  • 惟一索引: 這是單列索引和聯合索引的特例,不一樣的就是在整個表中僅在符合單列或者多列條件所指定的同一個/一組值的數據行,僅容許存在一條

在這裏須要特別說明的是聯合索引。筆者以前一直覺得聯合索引就是索引了一個字段以後,在獲得的結果中再對下一個字段進行索引。但後來查閱資料以後才知道其實並非。

當建立一個聯合索引時,索引中的每個字段的值,都會在索引的數據結構中出現。這裏我以爲這篇文章講得已經很是準確和簡要了,讀者能夠直接參閱。

覆蓋索引

「覆蓋索引」 並非一種索引的類別,而是一種查詢狀況。前文提到過,在大部分按照索引進行的查詢時,還須要進行回表查詢從而獲得客戶端所須要的其餘字段。可是前文也提到,若是你查詢的字段,當前的索引已經徹底覆蓋了,那麼這個時候 InnoDB 不會再進行多餘的回表查詢,而是在非聚簇索引查詢中就直接把字段返回了。這個現象就稱爲 「覆蓋索引」(covering index)。

空間索引

InnoDB 在 5.7.4 labs 版本中開始支持對空間索引的支持。簡單而言,咱們平時的索引就是一個緯度的,好比一個數字x。而空間索引則是對一個空間座標系的索引,好比 (x, y) 或者是 (x, y, z)。

InnoDB 的索引採用 R 樹,讀者感興趣的話能夠參閱相關資料進一步學習。在大部分的應用場景中,若是不涉及地理數據的話,空間索引咱們用得仍是比較少的。

回答面試題

好了,前面的面試題,咱們就能夠大體地回答出來了:

問: InnoDB 索引所使用的算法是什麼?
答: B+樹
問: 爲何 InnoDB 要使用 B+ 樹而不是其餘的數據結構呢?
答: 相比起紅黑樹,B樹的節點以頁爲單位,而頁則與硬盤中的頁相互綁定,所以能夠優化硬盤存取的效率
相比起紅黑樹,B樹的深度比較穩定,查找的耗時比較可預期——這個實際上是B樹的分裂和旋轉策略所決定的,讀者能夠進一步閱讀資料瞭解
相比起B樹,B+樹的葉子結點之間包含雙向鏈表,能夠極大地優化遍歷類和 offset - limit 類查詢的耗時
InnoDB 在使用 B+樹中,使用了非聚簇索引,這一算法能夠極大地減小索引所佔的空間,從而大大減小索引佔用的內存和硬盤空間,提升索引重建效率
其實這個答案不惟一,讀者若是感興趣還能夠進一步閱讀參考資料
問: 在 InnoDB 中,是否是必需要有主鍵?若是建表的時候不指定主鍵會怎樣?
答: 前文已經回答了:主鍵是必須有的,若是不指定的話,InnoDB 會自動建立一個6字節的自增ID
問: InnoDB 的主鍵和索引有什麼區別?
答: InnoDB 的主鍵是一種特殊的索引,也就是聚簇索引;而其餘的索引都是非聚簇索引。區別就是聚簇索引上保存的是完整的一行數據,而非聚簇索引上保存的是索引值以及主鍵

參考資料


本文章採用 知識共享署名-非商業性使用-相同方式共享 4.0 國際許可協議 進行許可。

原做者: amc,歡迎轉載,但請註明出處。

原文標題:小面試官教你 MySQL——引擎、索引和算法

發佈日期:2020-11-09

原文連接:https://cloud.tencent.com/developer/article/1336510,也是本人的博客

相關文章
相關標籤/搜索