爲了把mysql的索引底層原理講清楚,我把計算機翻了個底朝天

什麼是索引

概念:索引是提高mysql查詢效率的數據結構。總的一句話概括就是索引是一種數據結構。

數據庫查詢是數據庫的最主要功能之一。設計者們都希望查詢數據的速度能儘可能的快,因此數據庫系統的設計者會從查詢算法的角度進行優化。最基本的查詢算法當然是順序查找(linear search),這種複雜度爲O(n)的算法在數據量很大時顯然是糟糕的,好在計算機科學的發展提供了很多更優秀的查找算法,例如:有順序查找、折半查找、快速查找等。

但是每種查找算法都只能應用於特定的數據結構之上,例如順序查找依賴於順序結構,折半查找通過二叉查找樹或紅黑樹實現二分搜索。因此在數據之外,數據庫系統還維護着滿足特定查找算法的數據結構。這種數據結構,就是索引。

Mysql索引原理

目前大多數數據庫系統及文件系統都採用 B-Tree 或其變種 B+Tree 作爲索引結構。B+ 樹索引是 B+ 樹在數據庫中的一種實現,是最常見也是數據庫中使用最爲頻繁的一種索引。

從最早的平衡二叉樹演化而來的。B+ 樹是由二叉查找樹、平衡二叉樹(AVLTree)和平衡多路查找樹(B-Tree)逐步優化而來。

那麼爲什麼mysql的索引選擇B+數呢?

紅黑樹也可以作爲數據結構也可以用來實現索引,但是文件系統以及數據庫系統普遍採用B樹或者B+樹,這裏結合計算的組成原理來深入的分析。

一般來說,索引本身也很大,不可能全部存儲在內存中,因此索引往往以索引文件的形式存儲在磁盤上。這樣的話,索引查找過程中就要產生磁盤I/O消耗,相對於內存存取,硬盤I/O存取的消耗要高几個數量級,查找過程中磁盤I/O的存取次數。

爲什麼硬盤的存取會如此的慢呢?

這個就要講硬盤的讀寫原理,硬盤有很多種,但是都是由盤片、磁頭、盤片主軸、控制電機、磁頭控制器、數據轉換器、接口、緩存等幾個部分組成。

所有的盤片都固定在一條軸上,那條軸叫做盤片主軸,所有的盤片都是絕對平行的,也形成一個柱體,每個盤片上都有一個磁頭,每個磁頭都在同意軸線上,就是從上方往下看,磁頭是絕對重疊的。

所有的磁頭連在一個磁頭控制器上,由磁頭控制器負責各個磁頭的運動,磁頭可沿盤片的半徑方向移動,實際上是斜切運動,每個磁頭同一時刻必須是同軸的盤片以每分鐘數千轉到上萬轉的速度在高速運轉,這樣磁頭就能對盤片上的指定位置進行數據的讀寫操作。結構圖如下:

磁盤數據的讀寫原理

盤片被劃分成一系列同心環,圓心是盤片中心,每個同心環叫做一個磁道,所有半徑相同的磁道組成一個柱面。磁道被沿半徑線劃分成一個個小的段,每個段叫做一個扇區,每個扇區是磁盤的最小存儲單元。爲了簡單起見,我們下面假設磁盤只有一個盤片和一個磁頭。

當磁盤讀取數據時,系統會將數據邏輯地址傳給磁盤,磁盤的控制電路按照尋址邏輯將邏輯地址翻譯成物理地址,即確定要讀的數據在哪個磁道,哪個扇區。

爲了讀取這個扇區的數據,需要將磁頭放到這個扇區上方,爲了實現這一點,磁頭需要移動對準相應磁道,這個過程叫做尋道,所耗費時間叫做尋道時間,然後磁盤旋轉將目標扇區旋轉到磁頭下,這個過程耗費的時間叫做旋轉時間。

即一次磁盤的讀寫操作完成過程由三個動作組成:

  1. 尋道(時間):磁頭移動定位到指定磁道。
  2. 旋轉延遲(時間):等待指定扇區從磁頭下旋轉經過。
  3. 數據傳輸(時間):數據在磁盤與內存之間的實際傳輸

額外知識:
(1)盤面:硬盤的每一個盤片都有上下兩個盤面,一般每個盤面都會得到利用,都可以存儲數據,盤面號又叫磁頭號,因爲每一個有效盤面都有一個對應的讀寫磁頭。
(2)磁道:磁盤在格式化時被劃分成許多同心圓,這些同心圓軌跡叫做磁道,磁道從外向內從 0 開始順序編號,信息以脈衝串的形式記錄在這些軌跡中,這些同心圓不是連續記錄數據,而是被劃分成一段段的圓弧。
(3)所有盤面上的同一磁道構成一個圓柱,通常稱作柱面。所有盤面上的同一磁道構成一個圓柱,通常稱作柱面。數據的讀 / 寫按柱面進行,而不按盤面進行,當一個磁道寫滿數據後,就在同一柱面的下一個盤面來寫,一個柱面寫滿後,才移到下一個扇區開始寫數據,讀數據也按照這種方式進行,這樣就提高了硬盤的讀 / 寫效率。

提高磁盤數據讀寫原理

局部性原理與磁盤預讀。由於存儲介質的特性,磁盤本身存取就比主存慢很多,再加上機械運動耗費,磁盤的存取速度往往是主存的幾百分分之一,因此爲了提高效率,要儘量減少磁盤I/O。

爲了達到這個目的,磁盤往往不是嚴格按需讀取,而是每次都會預讀,即使只需要一個字節,磁盤也會從這個位置開始,順序向後讀取一定長度的數據放入內存。

這樣做的理論依據是計算機科學中著名的局部性原理:當一個數據被用到時,其附近的數據也通常會馬上被使用。

所以,程序運行期間所需要的數據通常應當比較集中。由於磁盤順序讀取的效率很高(不需要尋道時間,只需很少的旋轉時間),因此對於具有局部性的程序來說,預讀可以提高I/O效率。

預讀的長度一般爲頁(page)4k的整倍數。頁是計算機管理存儲器的邏輯塊,硬件及操作系統往往將主存和磁盤存儲區分割爲連續的大小相等的塊,每個存儲塊稱爲一頁(在許多操作系統中,頁得大小通常爲4k),主存和磁盤以頁爲單位交換數據。

當程序要讀取的數據不在主存中時,會觸發一個缺頁異常,此時系統會向磁盤發出讀盤信號,磁盤會找到數據的起始位置並向後連續讀取一頁或幾頁載入內存中,然後異常返回,程序繼續運行。

在硬盤中由於涉及到機械運動,所以一次的磁盤IO消耗的時間是非常大的,於內存的讀取速度相比,就好比光速與聲速的比較。那麼回到我們的主題上爲什麼使用B+樹作爲數據結構呢?

B樹、B-樹、B+樹、紅黑樹性能分析

對於B樹和、B-樹、B+樹這裏只做簡單的介紹,詳細的特性請看着一篇[]。

B樹性能分析:B樹是二叉查找平衡樹,但是B樹一個節點只存一個關鍵字,在大量數據的時候,B樹樹高非常大,性能低下。

B-樹性能分析:B-樹對B樹性能做了很大優化,但是B-樹在大量數據的時候,也會訪問到葉子節點,這樣性能也是大大降低。

根據B-Tree的定義,可知檢索一次最多需要訪問h個節點。數據庫系統的設計者巧妙利用了磁盤預讀原理,將一個節點的大小設爲等於一個頁,這樣每個節點只需要一次I/O就可以完全載入。

B-Tree中一次檢索最多需要h-1次I/O(根節點常駐內存)。一般實際應用中,出度d(樹中各個節點的度的最大值)是非常大的數字,通常超過100,因此樹高h非常小(通常不超過3)。

模擬查找關鍵字 36 的過程:

  1. 根據根節點找到磁盤塊 1,讀入內存。【磁盤 I/O 操作第 1 次】
  2. 比較關鍵字 36在區間(17,35),找到磁盤塊 1 的指針 P3。
  3. 根據 P3指針找到磁盤塊 4,讀入內存。【磁盤 I/O 操作第 2 次】
  4. 比較關鍵字 36在區間(65,87),找到磁盤塊 4 的指針 P1。
  5. 根據 P1 指針找到磁盤塊 9,讀入內存。【磁盤 I/O 操作第 3 次】
  6. 在磁盤塊 98中的關鍵字列表中找到關鍵字 36。

分析上面過程,發現需要 3 次磁盤 I/O 操作,和 3 次內存查找操作。而 3 次磁盤 I/O 操作是影響整個 B-Tree 查找效率的決定因素。

紅黑樹性能分析:而紅黑樹這種結構,h明顯要深的多。由於邏輯上很近的節點(父子)物理上可能很遠,無法利用局部性,所以紅黑樹的I/O漸進複雜度也爲O(h),效率明顯比B-Tree差很多。

B+樹性能分析:B+Tree 是在 B-Tree 基礎上的一種優化,使其更適合實現外存儲索引結構,InnoDB 存儲引擎就是用 B+Tree 實現其索引結構。

在 B+Tree 中,所有數據記錄節點都是按照鍵值大小順序存放在同一層的葉子節點上,而非葉子節點上只存儲 key 值信息,這樣可以大大加大每個節點存儲的 key 值數量,降低 B+Tree 的高度。

B+Tree更適合外存索引,從上面分析可以看到,d越大索引的性能越好,而出度的上限取決於節點內key和data的大小。

在B+樹的結構中,只在葉子節點存儲數據,在非葉子節點中只存儲的索引,在非葉子節點中可以有更大的空間儲存更多的索引,這樣B+樹的出度d就可以大大的增加,從而降低的B+樹的高度h,B樹中一個節點的大小爲一個page的大小,也就是一次IO的讀取,h越小IO的次數就可以減少:

dmax=floor(pagesize/(keysize+datasize+pointsize))

floor表示向下取整。由於B+Tree內節點去掉了data域,因此可以擁有更大的出度,擁有更好的性能。

Mysql的InnoDB的索引的結構如下圖所示:

在MySQL中,不同存儲引擎對索引的實現方式是不同的,Mysql有MyISAM和InnoDB兩個存儲引擎的索引實現方式,下面就來分別介紹這兩種存儲引擎。

Mysql引擎

MyISAM

在MyISAM儲存引擎中,數據和索引文件試試分開儲存的,Myisam 的存儲文件有三個,後綴名分別是 .frm、.MYD、MYI,其中 .frm 是表的定義文件,.MYD 是數據文件,.MYI 是索引文件。

Myisam 只支持表鎖,且不支持事務。Myisam 由於有單獨的索引文件,在讀取數據方面的性能很高 。

Myisam 也是B+樹結構,但是MyISAM索引的葉子節點的數據保存的是行數據的地址。因此,MyISAM中索引檢索的算法首先在索引樹中找到行數據的地址,然後根據地址找到對應的行數據。

可以看出MyISAM的索引文件僅僅保存數據記錄的地址。主鍵索引和輔助索引,只是主索引要求key是唯一的,而輔助索引的key可以重複。如果我們在Col2上建立一個輔助索引,則此索引的如下圖:

MyISAM的索引方式也叫做“非聚集”的,之所以這麼稱呼是爲了與InnoDB的聚集索引區分。

InnoDB

在InnoDB中,數據和索引文件是合起來儲存的,如圖所示,InnoDB 的存儲文件有兩個,後綴名分別是 .frm 和 .idb,其中 .frm 是表的定義文件,而 idb 是數據文件。

在InnoDB雖然底層也是B+樹實現的方式,當時與MyISAM卻有明顯的區別,在InnoDB實現的索引結構中,索引文件和數據文件是一起的,InnoDB中索引文件中的key就是數據表中的主鍵索引,因此InnoDB的索引文件也是主索引文件。如下圖所示:

在InnoDB中的葉子節點中把保存和完整的數據,這就是聚集索引。因爲InnoDB是按照主鍵聚集的,要是InnoDB沒有主鍵就會找數據表中的位置標誌的字段作爲主鍵,要是沒有這種字段就會隱世的生成唯一標識的主鍵,生成的主鍵默認爲長整型,6個字節。

而MyISAM可以要求沒有主鍵,這是這兩者的一個明顯的區別。另一個區別就是輔助索引的葉子節點的data域存儲的是主鍵的值,而不是行數據。

所以,當查詢不是按照主鍵查詢時候就會先在輔助索引樹上先找到主鍵的值,然後再到主索引樹找到對應的行數據的值,這叫做回表,回表降低了表的查詢效率。

如果給另一個字段指定爲普通索引,則普通索引樹的結構如下圖所示:

知道了索引的底層原理的實現還是有很大的幫助的,例如:主鍵至不要過大,因爲所有的普通索引都引用主索引,索引本身是佔內存的,若是索引過大,這樣就會大大影響查詢的效率。

InnoDB其它特點: 在InnoDB 中存在表鎖和行鎖,不過行鎖是在命中索引的情況下才會起作用,當索引失效時行鎖也會失效。InnoDB 支持事務,且支持四種隔離級別(讀未提交、讀已提交、可重複讀、串行化),默認的爲可重複讀;而在 Oracle 數據庫中,只支持串行化級別和讀已提交這兩種級別,其中默認的爲讀已提交級別。

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