深入理解 MySQL 索引底層原理

来源:https://www.cnblogs.com/88223100/archive/2022/12/24/Deeply-understand-the-underlying-principles-of-MySQL-indexing.html
-Advertisement-
Play Games

Mysql 作為互聯網中非常熱門的資料庫,其底層的存儲引擎和數據檢索引擎的設計非常重要,尤其是 Mysql 數據的存儲形式以及索引的設計,決定了 Mysql 整體的數據檢索性能。 ...


 

 

一步一步推導出 Mysql 索引的底層數據結構。

Mysql 作為互聯網中非常熱門的資料庫,其底層的存儲引擎和數據檢索引擎的設計非常重要,尤其是 Mysql 數據的存儲形式以及索引的設計,決定了 Mysql 整體的數據檢索性能。

我們知道,索引的作用是做數據的快速檢索,而快速檢索的實現的本質是數據結構。通過不同數據結構的選擇,實現各種數據快速檢索。在資料庫中,高效的查找演算法是非常重要的,因為資料庫中存儲了大量數據,一個高效的索引能節省巨大的時間。比如下麵這個數據表,如果 Mysql 沒有實現索引演算法,那麼查找 id=7 這個數據,那麼只能採取暴力順序遍歷查找,找到 id=7 這個數據需要比較 7 次,如果這個表存儲的是 1000W 個數據,查找 id=1000W 這個數據那就要比較 1000W 次,這種速度是不能接受的。

圖片

Mysql 索引底層數據結構選型

  1. 哈希表(Hash)

哈希表是做數據快速檢索的有效利器。

哈希演算法:也叫散列演算法,就是把任意值(key)通過哈希函數變換為固定長度的 key 地址,通過這個地址進行具體數據的數據結構。

圖片

考慮這個資料庫表 user,表中一共有 7 個數據,我們需要檢索 id=7 的數據,SQL 語法是:

select  *  from user where id = 7;

哈希演算法首先計算存儲 id=7 的數據的物理地址 addr=hash(7)=4231,而 4231 映射的物理地址是 0x77,0x77 就是 id=7 存儲的額數據的物理地址,通過該獨立地址可以找到對應 user_name='g'這個數據。這就是哈希演算法快速檢索數據的計算過程。

但是哈希演算法有個數據碰撞的問題,也就是哈希函數可能對不同的 key 會計算出同一個結果,比如 hash(7)可能跟 hash(199)計算出來的結果一樣,也就是不同的 key 映射到同一個結果了,這就是碰撞問題。解決碰撞問題的一個常見處理方式就是鏈地址法,即用鏈表把碰撞的數據接連起來。計算哈希值之後,還需要檢查該哈希值是否存在碰撞數據鏈表,有則一直遍歷到鏈表尾,直達找到真正的 key 對應的數據為止。

圖片圖片

從演算法時間複雜度分析來看,哈希演算法時間複雜度為 O(1),檢索速度非常快。比如查找 id=7 的數據,哈希索引只需要計算一次就可以獲取到對應的數據,檢索速度非常快。但是 Mysql 並沒有採取哈希作為其底層演算法,這是為什麼呢?

因為考慮到數據檢索有一個常用手段就是範圍查找,比如以下這個 SQL 語句:

select \* fromuserwhereid \>3;

針對以上這個語句,我們希望做的是找出 id>3 的數據,這是很典型的範圍查找。如果使用哈希演算法實現的索引,範圍查找怎麼做呢?一個簡單的思路就是一次把所有數據找出來載入到記憶體,然後再在記憶體里篩選篩選目標範圍內的數據。但是這個範圍查找的方法也太笨重了,沒有一點效率而言。

所以,使用哈希演算法實現的索引雖然可以做到快速檢索數據,但是沒辦法做數據高效範圍查找,因此哈希索引是不適合作為 Mysql 的底層索引的數據結構。

  1. 二叉查找樹(BST)

二叉查找樹是一種支持數據快速查找的數據結構,如圖下所示:

圖片

二叉查找樹的時間複雜度是 O(lgn),比如針對上面這個二叉樹結構,我們需要計算比較 3 次就可以檢索到 id=7 的數據,相對於直接遍歷查詢省了一半的時間,從檢索效率上看來是能做到高速檢索的。此外二叉樹的結構能不能解決哈希索引不能提供的範圍查找功能呢?

答案是可以的。觀察上面的圖,二叉樹的葉子節點都是按序排列的,從左到右依次升序排列,如果我們需要找 id>5 的數據,那我們取出節點為 6 的節點以及其右子樹就可以了,範圍查找也算是比較容易實現。

但是普通的二叉查找樹有個致命缺點:極端情況下會退化為線性鏈表,二分查找也會退化為遍歷查找,時間複雜退化為 O(N),檢索性能急劇下降。比如以下這個情況,二叉樹已經極度不平衡了,已經退化為鏈表了,檢索速度大大降低。此時檢索 id=7 的數據的所需要計算的次數已經變為 7 了。

圖片

在資料庫中,數據的自增是一個很常見的形式,比如一個表的主鍵是 id,而主鍵一般預設都是自增的,如果採取二叉樹這種數據結構作為索引,那上面介紹到的不平衡狀態導致的線性查找的問題必然出現。因此,簡單的二叉查找樹存在不平衡導致的檢索性能降低的問題,是不能直接用於實現 Mysql 底層索引的。

  1. AVL 樹和紅黑樹

二叉查找樹存在不平衡問題,因此學者提出通過樹節點的自動旋轉和調整,讓二叉樹始終保持基本平衡的狀態,就能保持二叉查找樹的最佳查找性能了。基於這種思路的自調整平衡狀態的二叉樹有 AVL 樹和紅黑樹。

首先簡單介紹紅黑樹,這是一顆會自動調整樹形態的樹結構,比如當二叉樹處於一個不平衡狀態時,紅黑樹就會自動左旋右旋節點以及節點變色,調整樹的形態,使其保持基本的平衡狀態(時間複雜度為 O(logn)),也就保證了查找效率不會明顯減低。比如從 1 到 7 升序插入數據節點,如果是普通的二叉查找樹則會退化成鏈表,但是紅黑樹則會不斷調整樹的形態,使其保持基本平衡狀態,如下圖所示。下麵這個紅黑樹下查找 id=7 的所要比較的節點數為 4,依然保持二叉樹不錯的查找效率。

紅黑樹擁有不錯的平均查找效率,也不存在極端的 O(n)情況,那紅黑樹作為 Mysql 底層索引實現是否可以呢?其實紅黑樹也存在一些問題,觀察下麵這個例子。

紅黑樹順序插入 1~7 個節點,查找 id=7 時需要計算的節點數為 4。

圖片

紅黑樹順序插入 1~16 個節點,查找 id=16 需要比較的節點數為 6 次。觀察一下這個樹的形態,是不是當數據是順序插入時,樹的形態一直處於“右傾”的趨勢呢?從根本上上看,紅黑樹並沒有完全解決二叉查找樹雖然這個“右傾”趨勢遠沒有二叉查找樹退化為線性鏈表那麼誇張,但是資料庫中的基本主鍵自增操作,主鍵一般都是數百萬數千萬的,如果紅黑樹存在這種問題,對於查找性能而言也是巨大的消耗,我們資料庫不可能忍受這種無意義的等待的。

圖片

現在考慮另一種更為嚴格的自平衡二叉樹 AVL 樹。因為 AVL 樹是個絕對平衡的二叉樹,因此他在調整二叉樹的形態上消耗的性能會更多。

AVL 樹順序插入 1~7 個節點,查找 id=7 所要比較節點的次數為 3。

圖片

AVL 樹順序插入 1~16 個節點,查找 id=16 需要比較的節點數為 4。從查找效率而言,AVL 樹查找的速度要高於紅黑樹的查找效率(AVL 樹是 4 次比較,紅黑樹是 6 次比較)。從樹的形態看來,AVL 樹不存在紅黑樹的“右傾”問題。也就是說,大量的順序插入不會導致查詢性能的降低,這從根本上解決了紅黑樹的問題。

圖片

總結一下 AVL 樹的優點:

  1. 不錯的查找性能(O(logn)),不存在極端的低效查找的情況。

  2. 可以實現範圍查找、數據排序。

看起來 AVL 樹作為數據查找的數據結構確實很不錯,但是 AVL 樹並不適合做 Mysql 資料庫的索引數據結構,因為考慮一下這個問題:

資料庫查詢數據的瓶頸在於磁碟 IO,如果使用的是 AVL 樹,我們每一個樹節點只存儲了一個數據,我們一次磁碟 IO 只能取出來一個節點上的數據載入到記憶體里,那比如查詢 id=7 這個數據我們就要進行磁碟 IO 三次,這是多麼消耗時間的。所以我們設計資料庫索引時需要首先考慮怎麼儘可能減少磁碟 IO 的次數。

磁碟 IO 有個有個特點,就是從磁碟讀取 1B 數據和 1KB 數據所消耗的時間是基本一樣的,我們就可以根據這個思路,我們可以在一個樹節點上儘可能多地存儲數據,一次磁碟 IO 就多載入點數據到記憶體,這就是 B 樹,B+樹的的設計原理了。

  1. B 樹

下麵這個 B 樹,每個節點限制最多存儲兩個 key,一個節點如果超過兩個 key 就會自動分裂。比如下麵這個存儲了 7 個數據 B 樹,只需要查詢兩個節點就可以知道 id=7 這數據的具體位置,也就是兩次磁碟 IO 就可以查詢到指定數據,優於 AVL 樹。

圖片

下麵是一個存儲了 16 個數據的 B 樹,同樣每個節點最多存儲 2 個 key,查詢 id=16 這個數據需要查詢比較 4 個節點,也就是經過 4 次磁碟 IO。看起來查詢性能與 AVL 樹一樣。

圖片

但是考慮到磁碟 IO 讀一個數據和讀 100 個數據消耗的時間基本一致,那我們的優化思路就可以改為:儘可能在一次磁碟 IO 中多讀一點數據到記憶體。這個直接反映到樹的結構就是,每個節點能存儲的 key 可以適當增加。

當我們把單個節點限制的 key 個數設置為 6 之後,一個存儲了 7 個數據的 B 樹,查詢 id=7 這個數據所要進行的磁碟 IO 為 2 次。

圖片

一個存儲了 16 個數據的 B 樹,查詢 id=7 這個數據所要進行的磁碟 IO 為 2 次。相對於 AVL 樹而言磁碟 IO 次數降低為一半。

圖片

所以資料庫索引數據結構的選型而言,B 樹是一個很不錯的選擇。總結來說,B 樹用作資料庫索引有以下優點:

  1. 優秀檢索速度,時間複雜度:B 樹的查找性能等於 O(h*logn),其中 h 為樹高,n 為每個節點關鍵詞的個數;

  2. 儘可能少的磁碟 IO,加快了檢索速度;

  3. 可以支持範圍查找。

5.B+樹

B 樹和 B+樹有什麼不同呢?

第一,B 樹一個節點里存的是數據,而 B+樹存儲的是索引(地址),所以 B 樹里一個節點存不了很多個數據,但是 B+樹一個節點能存很多索引,B+樹葉子節點存所有的數據。

第二,B+樹的葉子節點是數據階段用了一個鏈表串聯起來,便於範圍查找。

圖片

通過 B 樹和 B+樹的對比我們看出,B+樹節點存儲的是索引,在單個節點存儲容量有限的情況下,單節點也能存儲大量索引,使得整個 B+樹高度降低,減少了磁碟 IO。其次,B+樹的葉子節點是真正數據存儲的地方,葉子節點用了鏈表連接起來,這個鏈表本身就是有序的,在數據範圍查找時,更具備效率。因此 Mysql 的索引用的就是 B+樹,B+樹在查找效率、範圍查找中都有著非常不錯的性能。

 

Innodb 引擎和 Myisam 引擎的實現

Mysql 底層數據引擎以插件形式設計,最常見的是 Innodb 引擎和 Myisam 引擎,用戶可以根據個人需求選擇不同的引擎作為 Mysql 數據表的底層引擎。我們剛分析了,B+樹作為 Mysql 的索引的數據結構非常合適,但是數據和索引到底怎麼組織起來也是需要一番設計,設計理念的不同也導致了 Innodb 和 Myisam 的出現,各自呈現獨特的性能。

MyISAM 雖然數據查找性能極佳,但是不支持事務處理。Innodb 最大的特色就是支持了 ACID 相容的事務功能,而且他支持行級鎖。Mysql 建立表的時候就可以指定引擎,比如下麵的例子,就是分別指定了 Myisam 和 Innodb 作為 user 表和 user2 表的數據引擎。

圖片
圖片

執行這兩個指令後,系統出現了以下的文件,說明這兩個引擎數據和索引的組織方式是不一樣的。

圖片

Innodb 創建表後生成的文件有:

  • frm:創建表的語句

  • idb:表裡面的數據+索引文件

Myisam 創建表後生成的文件有

  • frm:創建表的語句

  • MYD:表裡面的數據文件(myisam data)

  • MYI:表裡面的索引文件(myisam index)

從生成的文件看來,這兩個引擎底層數據和索引的組織方式並不一樣,MyISAM 引擎把數據和索引分開了,一人一個文件,這叫做非聚集索引方式;Innodb 引擎把數據和索引放在同一個文件里了,這叫做聚集索引方式。下麵將從底層實現角度分析這兩個引擎是怎麼依靠 B+樹這個數據結構來組織引擎實現的。

  1. MyISAM 引擎的底層實現(非聚集索引方式)

MyISAM 用的是非聚集索引方式,即數據和索引落在不同的兩個文件上。MyISAM 在建表時以主鍵作為 KEY 來建立主索引 B+樹,樹的葉子節點存的是對應數據的物理地址。我們拿到這個物理地址後,就可以到 MyISAM 數據文件中直接定位到具體的數據記錄了。

圖片

當我們為某個欄位添加索引時,我們同樣會生成對應欄位的索引樹,該欄位的索引樹的葉子節點同樣是記錄了對應數據的物理地址,然後也是拿著這個物理地址去數據文件里定位到具體的數據記錄。

  1. Innodb 引擎的底層實現(聚集索引方式)

InnoDB 是聚集索引方式,因此數據和索引都存儲在同一個文件里。首先 InnoDB 會根據主鍵 ID 作為 KEY 建立索引 B+樹,如左下圖所示,而 B+樹的葉子節點存儲的是主鍵 ID 對應的數據,比如在執行 select * from user_info where id=15 這個語句時,InnoDB 就會查詢這顆主鍵 ID 索引 B+樹,找到對應的 user_name='Bob'。

這是建表的時候 InnoDB 就會自動建立好主鍵 ID 索引樹,這也是為什麼 Mysql 在建表時要求必須指定主鍵的原因。當我們為表裡某個欄位加索引時 InnoDB 會怎麼建立索引樹呢?比如我們要給 user_name 這個欄位加索引,那麼 InnoDB 就會建立 user_name 索引 B+樹,節點里存的是 user_name 這個 KEY,葉子節點存儲的數據的是主鍵 KEY。註意,葉子存儲的是主鍵 KEY!拿到主鍵 KEY 後,InnoDB 才會去主鍵索引樹里根據剛在 user_name 索引樹找到的主鍵 KEY 查找到對應的數據。

圖片

問題來了,為什麼 InnoDB 只在主鍵索引樹的葉子節點存儲了具體數據,但是其他索引樹卻不存具體數據呢,而要多此一舉先找到主鍵,再在主鍵索引樹找到對應的數據呢?

其實很簡單,因為 InnoDB 需要節省存儲空間。一個表裡可能有很多個索引,InnoDB 都會給每個加了索引的欄位生成索引樹,如果每個欄位的索引樹都存儲了具體數據,那麼這個表的索引數據文件就變得非常巨大(數據極度冗餘了)。從節約磁碟空間的角度來說,真的沒有必要每個欄位索引樹都存具體數據,通過這種看似“多此一舉”的步驟,在犧牲較少查詢的性能下節省了巨大的磁碟空間,這是非常有值得的。

在進行 InnoDB 和 MyISAM 特點對比時談到,MyISAM 查詢性能更好,從上面索引文件數據文件的設計來看也可以看出原因:MyISAM 直接找到物理地址後就可以直接定位到數據記錄,但是 InnoDB 查詢到葉子節點後,還需要再查詢一次主鍵索引樹,才可以定位到具體數據。等於 MyISAM 一步就查到了數據,但是 InnoDB 要兩步,那當然 MyISAM 查詢性能更高。

本文首先探討了哪種數據結構更適合作為 Mysql 底層索引的實現,然後再介紹了 Mysql 兩種經典數據引擎 MyISAM 和 InnoDB 的底層實現。最後再總結一下什麼時候需要給你的表裡的欄位加索引吧:

  1. 較頻繁的作為查詢條件的欄位應該創建索引;

  2. 唯一性太差的欄位不適合單獨創建索引,即使該欄位頻繁作為查詢條件;

  3. 更新非常頻繁的欄位不適合創建索引。

 

 

作者:junshili

本文來自博客園,作者:古道輕風,轉載請註明原文鏈接:https://www.cnblogs.com/88223100/p/Deeply-understand-the-underlying-principles-of-MySQL-indexing.html


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 前言 在學習《Python從入門到精通(第2版)》的第15章 GUI界面編程——15.2.4 將.ui文件轉換為.py文件時,按照書中步驟出錯時的問題解決,希望對同樣學習本書的同學有所幫助。 問題 問題出現 當跟著書15.2.4執行步驟(2)時PyCharm報錯 錯誤提示:pyuic5: error ...
  • 上一篇文章中我們聊了Caffeine的同步、非同步的數據回源方式。本篇文章我們再一起研討下經Caffeine改良過的非同步數據驅逐處理實現,以及Caffeine支持的多種不同的數據淘汰驅逐機制和對應的實際使用。 ...
  • 1.準備 先從github官網上clone elasticsearch源碼到本地,選擇合適的分支。筆者這裡選用的是7.4.0(與筆者工作環境使用的分支一致),此版本編譯需要jdk11。 2.編譯 Readme 中說明瞭編譯命令 ./gradlew assemble 執行此命令,等待1h左右即可,根據 ...
  • 目錄 1.目標圖 2.項目簡介 3.目錄結構 4.建立MySQL表 5.實現過程 5.1 index.php 5.2 data.php 5.2 method.php 5.3 case.php 5.4 main.js 5.5 css/style.css 5.6 img\icon01.png 5.7 j ...
  • JZ54二叉搜索樹的第k個節點 題目 給定一棵結點數為n 二叉搜索樹,請找出其中的第 k 小的TreeNode結點值。 返回第k小的節點值即可 不能查找的情況,如二叉樹為空,則返回-1,或者k大於n等等,也返回-1 保證n個節點的值不一樣 思路 演算法實現 根據二叉搜索樹的性質,左子樹的元素都小於根節 ...
  • 書接上回,前一篇我們在全平臺構建好了Ruby3的開發環境,現在,可以和Ruby3第一次親密接觸了。 Ruby是一門在面向對象層面無所不用其極的解釋型編程語言。 我們可以把編寫Ruby代碼看作是一場行為上的藝術,編碼就像跳舞一樣,Ruby的每一步都很優雅,幾乎沒有一步是多餘的。 第一行代碼 進入系統的 ...
  • 這是我大約半年前就想寫的隨筆。 功能很簡單。 就是基於Geometry的畫布,記錄滑鼠軌跡生成PathGeometry。再就是添加刪除Path的功能也就是path筆跡刪除。 目前是實現了兩種方式。 1 基於預覽擦除 2 實時擦除 兩者在具體技術上沒有任何的區別都是依靠Geometry.Combine ...
  • SQL優化中,有一條放之四海而皆準的既定方針,那就是:永遠以小數據驅動大數據。其本質其實就是以小的數據樣本作為驅動查詢能夠優化查詢效率,在SQL中,涉及到不同表數據的連接、轉移、或者合併,這些操作必須得有個數據集作為“帶頭”大哥,即驅動數據,而這個驅動數據最好是數據量最小的那一個。 內大外小 在討論 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...