大家都知道索引的重要性,基本用法在上章《最全面的mysql索引知識大盤點》已分享過,本章主要是探索索引的底層實現原理。當然了,我們還是以mysql爲基準進行探討。
目錄
前言:innodb和myisam的區別
|
InnoDB |
MyISAM |
簡介 |
由Innobase Oy公司開發。 支持事務安全的引擎,支持外鍵、行鎖、事務是他的最大特點。如果有大量的update和insert,建議使用InnoDB,特別是針對多個併發和QPS較高的情況。 |
默認表類型,它是基於傳統的ISAM類型,ISAM是Indexed Sequential Access Method (有索引的順序訪問方法) 的縮寫,它是存儲記錄和文件的標準方法。不是事務安全的,而且不支持外鍵,如果執行大量的select,insert MyISAM比較適合。 |
使用場景 |
在線事務處理(OLTP)型應用 |
在線分析處理(OLAP) 型應用 |
鎖差異 |
Innodb支持事務和行級鎖,是innodb的最大特色。 事務的ACID屬性,併發事務帶來的幾個問題:更新丟失,髒讀,不可重複讀,幻讀。 事務隔離級別:未提交讀(Read uncommitted),已提交讀(Read committed),可重複讀(Repeatable read),可序列化(Serializable) |
myisam只支持表級鎖,用戶在操作myisam表時,select,update,delete,insert語句都會給表自動加鎖,如果加鎖以後的表滿足insert併發的情況下,可以在表的尾部插入新的數據。也可以通過lock table命令來鎖表,這樣操作主要是可以模仿事務,但是消耗非常大,一般只在實驗演示中使用。 |
數據庫文件差異 |
innodb屬於索引組織表 innodb有兩種存儲方式,共享表空間存儲和多表空間存儲 兩種存儲方式的表結構和myisam一樣,以表名開頭,擴展名是.frm。 如果使用共享表空間,那麼所有表的數據文件和索引文件都保存在一個表空間裏,一個表空間可以有多個文件,通過innodb_data_file_path和innodb_data_home_dir參數設置共享表空間的位置和名字,一般共享表空間的名字叫ibdata1-n。 如果使用多表空間,那麼每個表都有一個表空間文件用於存儲每個表的數據和索引,文件名以表名開頭,以.ibd爲擴展名。 |
myisam屬於堆表 myisam在磁盤存儲上有三個文件,每個文件名以表名開頭,擴展名指出文件類型。 .frm 用於存儲表的定義 .MYD 用於存放數據 .MYI 用於存放表索引 myisam表還支持三種不同的存儲格式: 靜態表(默認,但是注意數據末尾不能有空格,會被去掉) 動態表 壓縮表 |
索引差異 |
1、關於自動增長 6、索引保存位置
|
InnoDB有兩類索引,聚集索引(Clustered Index)與普通索引(Secondary Index):
- InnoDB的每一個表都會有聚集索引
- 如果表沒有定義PK,則第一個非空unique列是聚集索引,否則,InnoDB會創建一個隱藏的row-id作爲聚集索引
- InnoDB的普通索引,實際上會掃描兩遍:第一遍,由普通索引找到PK,第二遍,由PK找到行記錄
聚集索引
數據文件本身就是索引文件,非葉子節點存放<key,address>,address就是下一層的地址,通過PK可以直接定位到數據地址
非聚簇索引
葉子節點上的data是主鍵(即聚簇索引的主鍵),而不是記錄所在地址,因爲記錄所在地址並不能保證一定不會變,但主鍵可以保證。定位流程第一步,由普通索引找到PK,第二步,由PK找到行記錄
1.物理磁盤知識
首先dbms本身就是一個文件管理系統,只是它的實現方式確實比較複雜,但本質上是要通過訪問磁盤才能完成數據的存儲與檢索。本着刨根問底的精神,就要分析文件是存儲及檢索的。
1.1基本概念
盤片 |
|
盤面 |
|
磁頭 |
|
磁道 |
|
柱面 |
|
扇區 |
標識符就是扇區頭標,包括組成扇區三維地址的三個數字:盤面號,柱面號,扇區號(塊號)。 數據段可分爲數據和保護數據的糾錯碼(ECC)。 |
磁盤塊/簇 |
|
Page |
|
磁盤容量計算
存儲容量 = 磁頭數 × 磁道(柱面)數 × 每道扇區數 × 每扇區字節數
某磁盤是一個 3個圓盤6個磁頭,7個柱面(每個盤片7個磁道) 的磁盤,每條磁道有12個扇區,所以此磁盤的容量爲:6 * 7 * 12 * 512 = 258048
1.2硬盤中的數據
信息存儲在硬盤裏,硬盤是由很多的盤片組成,通過盤片表面的磁性物質來存儲數據。
把盤片放在顯微鏡下放大,可以看到盤片表面是凹凸不平的,凸起的地方被磁化,代表數字 1,凹的地方沒有被磁化,代表數字 0,因此硬盤可以通過二進制的形式來存儲表示文字、圖片等的信息。
所有的盤片都固定在一個旋轉軸上,這個軸即盤片主軸,所有的盤片之間是絕對平行的,在每個盤片的盤面上都有一個磁頭,磁頭與盤片之間的距離比頭髮絲的直徑還小。
所有的磁頭連在一個磁頭控制器上,由磁頭控制器負責各個磁頭的運動,磁頭可沿盤片的半徑方向移動,實際上是斜切運動,每個磁頭同一時刻必須是同軸的,即從正上方往下看,所有磁頭任何時候都是重疊的。
由於技術的發展,目前已經有多磁頭獨立技術了,在此不考慮此種情況。
盤片以每分鐘數千轉到上萬轉的速度在高速運轉,這樣磁頭就能對盤片上的指定位置進行數據的讀寫操作。
由於硬盤是高精密設備,塵埃是其大敵,所以必須完全密封。
1.3磁盤的讀寫原理
系統將文件存儲到磁盤上時,按柱面、磁頭、扇區的方式進行,即最先是第1磁道的第一磁頭下的所有扇區,然後是同一柱面的下一個磁頭……
一個柱面存儲滿後就推進到下一個柱面,直到把文件內容全部寫入磁盤。
系統也以相同的順序讀出數據,讀出數據時通過告訴磁盤控制器要讀出扇區所在柱面號、磁頭號和扇區號(物理地址的三個組成部分)進行。
注:操作系統讀取同理,只是顆粒的更大的塊操作
1.5磁盤的讀取響應時間
當需要從磁盤讀取數據的時候,系統會將數據的邏輯地址傳遞個磁盤,磁盤的控制電路按照尋址邏輯將邏輯地址翻譯成物理地址,即確定要讀的數據在哪個磁道,哪個扇區。
首先必須找到柱面,即磁頭需要移動對準相應磁道,這個過程叫做尋道。
然後目標扇區旋轉到磁頭下,即磁盤旋轉將目標扇區旋轉到磁頭下。
尋道(時間):磁頭移動定位到指定磁道所需要的時間,尋道時間越短,I/O操作越快,目前磁盤的平均尋道時間一般在3-15ms,一般都在10ms左右。
旋轉延遲(時間):盤片旋轉將請求數據所在扇區移至讀寫磁頭下方所需要的時間,旋轉延遲取決於磁盤轉速。普通硬盤一般都是7200rpm,慢的5400rpm。
數據傳輸(時間):數據在磁盤與內存之間的實際傳輸所需要的時間。
- 確定磁盤地址(柱面號,磁頭號,扇區號),內存地址(源/目):
- 爲了讀取這個扇區的數據,需要將磁頭放到這個扇區上方,爲了實現這一點:
- 即一次訪盤請求(讀 / 寫)完成過程由三個動作組成:
注:讀寫一次磁盤信息所需的時間中軟件應着重考慮減少尋道時間和延遲時間。
1.6 I/O 的預讀與局部性原理
由於存儲介質的特性,磁盤本身存取就比主存慢很多,再加上機械運動耗費的時間,磁盤的存取速度往往是主存的幾百分之一。
因此,計算機科學中著名的局部性原理:
-
當一個數據被用到時,其附近的數據一般來說也會被馬上使用。
-
程序運行期間所需要的數據通常比較集中。
-
由於磁盤順序讀取的效率很高(不需要尋道時間,只需要很少的旋轉時間),因此對於具有局部性的程序來說,預讀可以提高 I/O 效率。
預讀的長度一般爲頁(在許多操作系統中,頁的大小通常爲 4k)的整數倍。操作系統以內存頁爲單位管理內存,內存頁的大小對系統性能有影響。當程序要讀取的數據不在主存中時,會觸發一個缺頁異常,此時系統會向磁盤發出讀盤信息,磁盤會找到數據的起始位置並向後連續讀取一頁或幾頁的數據載入內存中(系統從磁盤讀取數據時是以磁盤塊爲基本單位的,位於同一磁盤塊中的數據會被一次性讀取出來,而不是按需讀取),然後異常返回,程序繼續運行。
2.推理並拆解普通查詢語句
select * from talbe_name where id=1
step1:找到數據文件
step2:讀取數據文件
step3:讀取id=1的數據
理論上是這樣的,索引是一種用來實現高效獲取數據的數據結構,建索引的目的是爲了查找的優化,特別是當數據很龐大的時候,非常重要。一般的查找算法有順序查找、折半查找、快速查找等,但是每種查找算法只能應用於特定的數據結構,例如順序查找依賴於順序結構,折半查找通過二叉查找樹或紅黑樹實現二分搜索。因此在數據之外,數據庫系統還維護着滿足特定查找算法的數據結構,它以某種方式引用數據。
3.爲什麼要用B+Tree實現
目前大多數數據庫系統及文件系統都採用 B-Tree 或其變種 B+Tree 作爲索引結構。B+ 樹中的 B (balance)代表平衡,而不是二叉。B+ 樹是從最早的平衡二叉樹演化而來的。B+ 樹是由二叉查找樹、平衡二叉樹(AVLTree)和平衡多路查找樹(B-Tree)逐步優化而來。
- 二叉查找樹:左子樹的鍵值小於根的鍵值,右子樹的鍵值大於根的鍵值。
- AVL 樹:平衡二叉樹(AVL 樹)在符合二叉查找樹的條件下,還滿足任何節點的兩個子樹的高度最大差爲 1,但不是紅黑樹。
- 平衡多路查找樹(B-Tree):爲磁盤等外存儲設備設計的一種平衡查找樹。
那麼糾結該如何選型呢?
索引的標準:IO漸進複雜度,說白了就是推演過程(每個節點都是1次IO),B+樹是爲磁盤及其他存儲輔助設備而設計一種平衡查找樹(不是二叉樹),所有記錄的節點按大小順序存放在同一層的葉節點中,各葉節點用指針進行連接。
InnoDB 存儲引擎使用頁作爲數據讀取單位,頁是其磁盤管理的最小單位,默認 page 大小是 16k。系統的一個磁盤塊的存儲空間往往沒有這麼大,因此 InnoDB 每次申請磁盤空間時都會是若干地址連續磁盤塊來達到頁的大小 16KB。在查詢數據時如果一個頁中的每條數據都能助於定位數據記錄的位置,這將會減少磁盤 I/O 的次數,提高查詢效率。
3.1 B-Tree
B-樹是一種多路自平衡的搜索樹 它類似普通的平衡二叉樹,不同的一點是B-樹允許每個節點有更多的子節點。B-Tree 相對於 AVLTree 縮減了節點個數,使每次磁盤 I/O 取到內存的數據都發揮了作用,從而提高了查詢效率。
注:B-Tree就是我們常說的B樹,一定不要讀成B減樹,否則就很丟人了
那麼m階 B-Tree 是滿足下列條件的數據結構:
- 所有鍵值分佈在整顆樹中
- 搜索有可能在非葉子結點結束,在關鍵字全集內做一次查找,性能逼近二分查找
- 每個節點最多擁有m個子樹
- 根節點至少有2個子樹
- 分支節點至少擁有m/2顆子樹(除根節點和葉子節點外都是分支節點)
- 所有葉子節點都在同一層、每個節點最多可以有m-1個key,並且以升序排列
每個節點佔用一個磁盤塊,一個節點上有兩個升序排序的關鍵字和三個指向子樹根節點的指針,指針存儲的是子節點所在磁盤塊的地址。兩個關鍵詞劃分成的三個範圍域對應三個指針指向的子樹的數據的範圍域。以根節點爲例,關鍵字爲 17 和 35,P1 指針指向的子樹的數據範圍爲小於 17,P2 指針指向的子樹的數據範圍爲 17~35,P3 指針指向的子樹的數據範圍爲大於 35。
模擬查找關鍵字 29 的過程:
- 根據根節點找到磁盤塊 1,讀入內存。【磁盤 I/O 操作第 1 次】
- 比較關鍵字 29 在區間(17,35),找到磁盤塊 1 的指針 P2。
- 根據 P2 指針找到磁盤塊 3,讀入內存。【磁盤 I/O 操作第 2 次】
- 比較關鍵字 29 在區間(26,30),找到磁盤塊 3 的指針 P2。
- 根據 P2 指針找到磁盤塊 8,讀入內存。【磁盤 I/O 操作第 3 次】
- 在磁盤塊 8 中的關鍵字列表中找到關鍵字 29。
分析上面過程,發現需要 3 次磁盤 I/O 操作,和 3 次內存查找操作。由於內存中的關鍵字是一個有序表結構,可以利用二分法查找提高效率。而 3 次磁盤 I/O 操作是影響整個 B-Tree 查找效率的決定因素。
但同時B-Tree也存在問題:
- 每個節點中有key,也有data,而每一個頁的存儲空間是有限的,如果data數據較大時將會導致每個節點(即一個頁)能存儲的 key 的數量很小。
- 當存儲的數據量很大時同樣會導致 B-Tree 的深度較大,增大查詢時的磁盤 I/O 次數,進而影響查詢效率
3.2 B+Tree
B+Tree 是在 B-Tree 基礎上的一種優化,InnoDB 存儲引擎就是用 B+Tree 實現其索引結構。它帶來的變化點:
- B+樹每個節點可以包含更多的節點,這樣做有兩個原因,一個是降低樹的高度。另外一個是將數據範圍變爲多個區間,區間越多,數據檢索越快
- 非葉子節點存儲key,葉子節點存儲key和數據
- 葉子節點兩兩指針相互鏈接(符合磁盤的預讀特性),順序查詢性能更高
注:MySQL 的 InnoDB 存儲引擎在設計時是將根節點常駐內存的,因此力求達到樹的深度不超過 3,也就是說 I/O 不需要超過 3 次。
通常在B+Tree上有兩個頭指針,一個指向根節點,另一個指向關鍵字最小的葉子節點,而且所有葉子節點(即數據節點)之間是一種鏈式環結構。因此可以對 B+Tree 進行兩種查找運算:一種是對於主鍵的範圍查找和分頁查找,另一種是從根節點開始,進行隨機查找。
3.3 B-樹和B+樹的區別
- B+樹內節點不存儲數據,所有數據存儲在葉節點導致查詢時間複雜度固定爲 log n
- B-樹查詢時間複雜度不固定,與 key 在樹中的位置有關,最好爲O(1)
- B+樹葉節點兩兩相連可大大增加區間訪問性,可使用在範圍查詢等
B-樹每個節點 key 和 data 在一起,則無法區間查找- B+樹更適合外部存儲(存儲磁盤數據)。由於內節點無 data 域,每個節點能索引的範圍更大更精確。
3.4 MongoDB 爲什麼使用B-樹
B-樹查詢時間複雜度不固定,與 key 在樹中的位置有關,最好爲O(1)。儘可能少的磁盤 IO 是提高性能的有效手段。MongoDB 是聚合型數據庫,而 B-樹恰好 key 和 data 域聚合在一起。
至於MongoDB爲什麼使用B-樹而不是B+樹,可以從它的設計角度來考慮,它並不是傳統的關係性數據庫,而是以Json格式作爲存儲的nosql,目的就是高性能,高可用,易擴展。首先它擺脫了關係模型,上面所述的優點2需求就沒那麼強烈了,其次Mysql由於使用B+樹,數據都在葉節點上,每次查詢都需要訪問到葉節點,而MongoDB使用B-樹,所有節點都有Data域,只要找到指定索引就可以進行訪問,無疑單次查詢平均快於Mysql。
4.Mysql索引是如何實現的
4.1 InnoDB 中的 B+Tree
InnoDB 是通過 B+Tree 結構對 ID 建索引,然後在葉子節點中存儲記錄。採用 InnoDB 引擎的數據存儲文件有兩個,一個定義文件,一個是數據文件。
若建索引的字段不是主鍵 ID,則對該字段建索引,然後在葉子節點中存儲的是該記錄的主鍵,然後通過主鍵索引找到對應的記錄。
4.2 Myisam 中的 B+Tree
Myisam 引擎也是採用的 B+Tree 結構來作爲索引結構。由於 Myisam 中的索引和數據分別存放在不同的文件,所以在索引樹中的葉子節點中存的數據是該索引對應的數據記錄的地址,由於數據與索引不在一起,所以 Myisam 是非聚簇索引。
引用資料
- File Space Management
- Externally Stored Fields in InnoDB
- InnoDB Record Structure
- InnoDB Page Structure