您好,登錄后才能下訂單哦!
這篇文章主要為大家展示了“Mysql索引實現原理的示例分析”,內容簡而易懂,條理清晰,希望能夠幫助大家解決疑惑,下面讓小編帶領大家一起研究并學習一下“Mysql索引實現原理的示例分析”這篇文章吧。
MySQL目前主要有以下幾種索引類型:
1.普通索引
2.唯一索引
3.主鍵索引
4.組合索引
5.全文索引
CREATE TABLE table_name[col_name data type] [unique|fulltext][index|key][index_name](col_name[length])[asc|desc]
1.unique|fulltext為可選參數,分別表示唯一索引、全文索引
2.index和key為同義詞,兩者作用相同,用來指定創建索引
3.col_name為需要創建索引的字段列,該列必須從數據表中該定義的多個列中選擇
4.index_name指定索引的名稱,為可選參數,如果不指定,默認col_name為索引值
5.length為可選參數,表示索引的長度,只有字符串類型的字段才能指定索引長度
6.asc或desc指定升序或降序的索引值存儲
1.普通索引
是最基本的索引,它沒有任何限制。它有以下幾種創建方式:
(1)直接創建索引
CREATE INDEX index_name ON table(column(length))
(2)修改表結構的方式添加索引
ALTER TABLE table_name ADD INDEX index_name ON (column(length))
(3)創建表的時候同時創建索引
CREATE TABLE `table` ( `id` int(11) NOT NULL AUTO_INCREMENT , `title` char(255) CHARACTER NOT NULL , `content` text CHARACTER NULL , `time` int(10) NULL DEFAULT NULL , PRIMARY KEY (`id`), INDEX index_name (title(length)) )
(4)刪除索引
DROP INDEX index_name ON table
2.唯一索引
與前面的普通索引類似,不同的就是:索引列的值必須唯一,但允許有空值。如果是組合索引,則列值的組合必須唯一。它有以下幾種創建方式:
(1)創建唯一索引
CREATE UNIQUE INDEX indexName ON table(column(length))
(2)修改表結構
ALTER TABLE table_name ADD UNIQUE indexName ON (column(length))
(3)創建表的時候直接指定
CREATE TABLE `table` ( `id` int(11) NOT NULL AUTO_INCREMENT , `title` char(255) CHARACTER NOT NULL , `content` text CHARACTER NULL , `time` int(10) NULL DEFAULT NULL , UNIQUE indexName (title(length)) );
3.主鍵索引
是一種特殊的唯一索引,一個表只能有一個主鍵,不允許有空值。一般是在建表的時候同時創建主鍵索引:
CREATE TABLE `table` ( `id` int(11) NOT NULL AUTO_INCREMENT , `title` char(255) NOT NULL , PRIMARY KEY (`id`) );
4.組合索引
指多個字段上創建的索引,只有在查詢條件中使用了創建索引時的第一個字段,索引才會被使用。使用組合索引時遵循最左前綴集合
ALTER TABLE `table` ADD INDEX name_city_age (name,city,age);
5.全文索引
主要用來查找文本中的關鍵字,而不是直接與索引中的值相比較。fulltext索引跟其它索引大不相同,它更像是一個搜索引擎,而不是簡單的where語句的參數匹配。fulltext索引配合match against操作使用,而不是一般的where語句加like。它可以在create table,alter table ,create index使用,不過目前只有char、varchar,text 列上可以創建全文索引。值得一提的是,在數據量較大時候,現將數據放入一個沒有全局索引的表中,然后再用CREATE index創建fulltext索引,要比先為一張表建立fulltext然后再將數據寫入的速度快很多。
(1)創建表的適合添加全文索引
CREATE TABLE `table` ( `id` int(11) NOT NULL AUTO_INCREMENT , `title` char(255) CHARACTER NOT NULL , `content` text CHARACTER NULL , `time` int(10) NULL DEFAULT NULL , PRIMARY KEY (`id`), FULLTEXT (content) );
(2)修改表結構添加全文索引
ALTER TABLE article ADD FULLTEXT index_content(content)
(3)直接創建索引
CREATE FULLTEXT INDEX index_content ON article(content)
1.雖然索引大大提高了查詢速度,同時卻會降低更新表的速度,如對表進行insert、update和delete。因為更新表時,不僅要保存數據,還要保存一下索引文件。
2.建立索引會占用磁盤空間的索引文件。一般情況這個問題不太嚴重,但如果你在一個大表上創建了多種組合索引,索引文件的會增長很快。
索引只是提高效率的一個因素,如果有大數據量的表,就需要花時間研究建立最優秀的索引,或優化查詢語句。
使用索引時,有以下一些技巧和注意事項:
1.索引不會包含有null值的列
只要列中包含有null值都將不會被包含在索引中,復合索引中只要有一列含有null值,那么這一列對于此復合索引就是無效的。所以我們在數據庫設計時不要讓字段的默認值為null。
2.使用短索引
對串列進行索引,如果可能應該指定一個前綴長度。例如,如果有一個char(255)的列,如果在前10個或20個字符內,多數值是惟一的,那么就不要對整個列進行索引。短索引不僅可以提高查詢速度而且可以節省磁盤空間和I/O操作。
3.索引列排序
查詢只使用一個索引,因此如果where子句中已經使用了索引的話,那么order by中的列是不會使用索引的。因此數據庫默認排序可以符合要求的情況下不要使用排序操作;盡量不要包含多個列的排序,如果需要最好給這些列創建復合索引。
4.like語句操作
一般情況下不推薦使用like操作,如果非使用不可,如何使用也是一個問題。like “%aaa%” 不會使用索引而like “aaa%”可以使用索引。
5.不要在列上進行運算
這將導致索引失效而進行全表掃描,例如
SELECT * FROM table_name WHERE YEAR(column_name)<2017;
6.不使用not in和<>操作
本文以MySQL數據庫為研究對象,討論與數據庫索引相關的一些話題。特別需要說明的是,MySQL支持諸多存儲引擎,而各種存儲引擎對索引的支持也各不相同,因此MySQL數據庫支持多種索引類型,如BTree索引,哈希索引,全文索引等等。為了避免混亂,本文將只關注于BTree索引,因為這是平常使用MySQL時主要打交道的索引,至于哈希索引和全文索引本文暫不討論。
文章主要內容分為三個部分。
第一部分主要從數據結構及算法理論層面討論MySQL數據庫索引的數理基礎。
第二部分結合MySQL數據庫中MyISAM和InnoDB數據存儲引擎中索引的架構實現討論聚集索引、非聚集索引及覆蓋索引等話題。
第三部分根據上面的理論基礎,討論MySQL中高性能使用索引的策略。
MySQL官方對索引的定義為:索引(Index)是幫助MySQL高效獲取數據的數據結構。提取句子主干,就可以得到索引的本質:索引是數據結構。
我們知道,數據庫查詢是數據庫的最主要功能之一。我們都希望查詢數據的速度能盡可能的快,因此數據庫系統的設計者會從查詢算法的角度進行優化。最基本的查詢算法當然是 順序查找(linear search),這種復雜度為O(n)的算法在數據量很大時顯然是糟糕的,好在計算機科學的發展提供了很多更優秀的查找算法,例如 二分查找(binary search)、 二叉樹查找(binary tree search)等。如果稍微分析一下會發現,每種查找算法都只能應用于特定的數據結構之上,例如二分查找要求被檢索數據有序,而二叉樹查找只能應用于 二叉查找樹上,但是數據本身的組織結構不可能完全滿足各種數據結構(例如,理論上不可能同時將兩列都按順序進行組織),所以,在數據之外,數據庫系統還維護著滿足特定查找算法的數據結構,這些數據結構以某種方式引用(指向)數據,這樣就可以在這些數據結構上實現高級查找算法。這種數據結構,就是索引。
看一個例子:
圖1
圖1展示了一種可能的索引方式。左邊是數據表,一共有兩列七條記錄,最左邊的是數據記錄的物理地址(注意邏輯上相鄰的記錄在磁盤上也并不是一定物理相鄰的)。為了加快Col2的查找,可以維護一個右邊所示的二叉查找樹,每個節點分別包含索引鍵值和一個指向對應數據記錄物理地址的指針,這樣就可以運用二叉查找在O(log2n)O(log2n)的復雜度內獲取到相應數據。
雖然這是一個貨真價實的索引,但是實際的數據庫系統幾乎沒有使用二叉查找樹或其進化品種 紅黑樹(red-black tree)實現的,原因會在下文介紹。
目前大部分數據庫系統及文件系統都采用B-Tree或其變種B+Tree作為索引結構,在本文的下一節會結合存儲器原理及計算機存取原理討論為什么B-Tree和B+Tree在被如此廣泛用于索引,這一節先單純從數據結構角度描述它們。
為了描述B-Tree,首先定義一條數據記錄為一個二元組[key, data],key為記錄的鍵值,對于不同數據記錄,key是互不相同的;data為數據記錄除key外的數據。那么B-Tree是滿足下列條件的數據結構:
d為大于1的一個正整數,稱為B-Tree的度。
h為一個正整數,稱為B-Tree的高度。
每個非葉子節點由n-1個key和n個指針組成,其中d<=n<=2d。
每個葉子節點最少包含一個key和兩個指針,最多包含2d-1個key和2d個指針,葉節點的指針均為null 。
key和指針互相間隔,節點兩端是指針。
一個節點中的key從左到右非遞減排列。
所有節點組成樹結構。
每個指針要么為null,要么指向另外一個節點。
如果某個指針在節點node最左邊且不為null,則其指向節點的所有key小于v(key1)v(key1),其中v(key1)v(key1)為node的第一個key的值。
如果某個指針在節點node最右邊且不為null,則其指向節點的所有key大于v(keym)v(keym),其中v(keym)v(keym)為node的最后一個key的值。
如果某個指針在節點node的左右相鄰key分別是keyikeyi和keyi+1keyi+1且不為null,則其指向節點的所有key小于v(keyi+1)v(keyi+1)且大于v(keyi)v(keyi)。
圖2是一個d=2的B-Tree示意圖。
圖2
由于B-Tree的特性,在B-Tree中按key檢索數據的算法非常直觀:首先從根節點進行二分查找,如果找到則返回對應節點的data,否則對相應區間的指針指向的節點遞歸進行查找,直到找到節點或找到null指針,前者查找成功,后者查找失敗。B-Tree上查找算法的偽代碼如下:
BTree_Search(node, key) {
if(node == null) return null;
foreach(node.key)
{
if(node.key[i] == key) return node.data[i];
if(node.key[i] > key) return BTree_Search(point[i]->node);
}
return BTree_Search(point[i+1]->node);
}
data = BTree_Search(root, my_key);
關于B-Tree有一系列有趣的性質,例如一個度為d的B-Tree,設其索引N個key,則其樹高的上限為logd((N+1)/2)logd((N+1)/2),檢索一個key,其查找節點個數的漸進復雜度為O(logdN)O(logdN)。從這點可以看出,B-Tree是一個非常有效率的索引數據結構。
另外,由于插入刪除新的數據記錄會破壞B-Tree的性質,因此在插入刪除時,需要對樹進行一個分裂、合并、轉移等操作以保持B-Tree性質,本文不打算完整討論B-Tree這些內容,因為已經有許多資料詳細說明了B-Tree的數學性質及插入刪除算法,有興趣的朋友可以在本文末的參考文獻一欄找到相應的資料進行閱讀。
B-Tree有許多變種,其中最常見的是B+Tree,例如MySQL就普遍使用B+Tree實現其索引結構。
與B-Tree相比,B+Tree有以下不同點:
每個節點的指針上限為2d而不是2d+1。
內節點不存儲data,只存儲key;葉子節點不存儲指針。
圖3是一個簡單的B+Tree示意。
圖3
由于并不是所有節點都具有相同的域,因此B+Tree中葉節點和內節點一般大小不同。這點與B-Tree不同,雖然B-Tree中不同節點存放的key和指針可能數量不一致,但是每個節點的域和上限是一致的,所以在實現中B-Tree往往對每個節點申請同等大小的空間。
一般來說,B+Tree比B-Tree更適合實現外存儲索引結構,具體原因與外存儲器原理及計算機存取原理有關,將在下面討論。
一般在數據庫系統或文件系統中使用的B+Tree結構都在經典B+Tree的基礎上進行了優化,增加了順序訪問指針。
圖4
如圖4所示,在B+Tree的每個葉子節點增加一個指向相鄰葉子節點的指針,就形成了帶有順序訪問指針的B+Tree。做這個優化的目的是為了提高區間訪問的性能,例如圖4中如果要查詢key為從18到49的所有數據記錄,當找到18后,只需順著節點和指針順序遍歷就可以一次性訪問到所有數據節點,極大提到了區間查詢效率。
這一節對B-Tree和B+Tree進行了一個簡單的介紹,下一節結合存儲器存取原理介紹為什么目前B+Tree是數據庫系統實現索引的首選數據結構。
上文說過,紅黑樹等數據結構也可以用來實現索引,但是文件系統及數據庫系統普遍采用B-/+Tree作為索引結構,這一節將結合計算機組成原理相關知識討論B-/+Tree作為索引的理論基礎。
一般來說,索引本身也很大,不可能全部存儲在內存中,因此索引往往以索引文件的形式存儲的磁盤上。這樣的話,索引查找過程中就要產生磁盤I/O消耗,相對于內存存取,I/O存取的消耗要高幾個數量級,所以評價一個數據結構作為索引的優劣最重要的指標就是在查找過程中磁盤I/O操作次數的漸進復雜度。換句話說,索引的結構組織要盡量減少查找過程中磁盤I/O的存取次數。下面先介紹內存和磁盤存取原理,然后再結合這些原理分析B-/+Tree作為索引的效率。
磁盤中有兩個機械運動的部分,分別是盤片旋轉和磁臂移動。盤片旋轉就是我們市面上所提到的多少轉每分鐘,而磁盤移動則是在盤片旋轉到指定位置以后,移動磁臂后開始進行數據的讀寫。那么這就存在一個定位到磁盤中的塊的過程,而定位是磁盤的存取中花費時間比較大的一塊,畢竟機械運動花費的時候要遠遠大于電子運動的時間。當大規模數據存儲到磁盤中的時候,顯然定位是一個非常花費時間的過程,但是我們可以通過B樹進行優化,提高磁盤讀取時定位的效率。
為什么B類樹可以進行優化呢?我們可以根據B類樹的特點,構造一個多階的B類樹,然后在盡量多的在結點上存儲相關的信息,保證層數盡量的少,以便后面我們可以更快的找到信息,磁盤的I/O操作也少一些,而且B類樹是平衡樹,每個結點到葉子結點的高度都是相同,這也保證了每個查詢是穩定的。
這里的B樹,也就是英文中的B-Tree,一個 m 階的B樹滿足以下條件:
每個結點至多擁有m棵子樹;
根結點至少擁有兩顆子樹(存在子樹的情況下);
除了根結點以外,其余每個分支結點至少擁有 m/2 棵子樹;
所有的葉結點都在同一層上;
有 k 棵子樹的分支結點則存在 k-1 個關鍵碼,關鍵碼按照遞增次序進行排列;
關鍵字數量需要滿足ceil(m/2)-1 <= n <= m-1;
舉個栗子:
B樹上大部分的操作所需要的磁盤存取次數和B樹的高度是成正比的,在B樹中可以檢查多個子結點,由于在一棵樹中檢查任意一個結點都需要一次磁盤訪問,所以B樹避免了大量的磁盤訪問。
既然是樹,那么必不可少的操作就是插入和刪除,這也是B樹和其它數據結構不同的地方,當然了,還有必不可少的搜索,分享一個對B樹的操作進行可視化的 網址,它是由usfca提供的。
假定對高度為h的m階B樹進行操作。
新結點一般插在第h層,通過搜索找到對應的結點進行插入,那么根據即將插入的結點的數量又分為下面幾種情況。
如果該結點的關鍵字個數沒有到達m-1個,那么直接插入即可;
如果該結點的關鍵字個數已經到達了m-1個,那么根據B樹的性質顯然無法滿足,需要將其進行分裂。分裂的規則是該結點分成兩半,將中間的關鍵字進行提升,加入到父親結點中,但是這又可能存在父親結點也滿員的情況,則不得不向上進行回溯,甚至是要對根結點進行分裂,那么整棵樹都加了一層。
其過程如下:
同樣的,我們需要先通過搜索找到相應的值,存在則進行刪除,需要考慮刪除以后的情況,
如果該結點擁有關鍵字數量仍然滿足B樹性質,則不做任何處理;
如果該結點在刪除關鍵字以后不滿足B樹的性質(關鍵字沒有到達ceil(m/2)-1的數量),則需要向兄弟結點借關鍵字,這有分為兄弟結點的關鍵字數量是否足夠的情況。
如果兄弟結點的關鍵字足夠借給該結點,則過程為將父親結點的關鍵字下移,兄弟結點的關鍵字上移;
如果兄弟結點的關鍵字在借出去以后也無法滿足情況,即之前兄弟結點的關鍵字的數量為ceil(m/2)-1,借的一方的關鍵字數量為ceil(m/2)-2的情況,那么我們可以將該結點合并到兄弟結點中,合并之后的子結點數量少了一個,則需要將父親結點的關鍵字下放,如果父親結點不滿足性質,則向上回溯;
其余情況參照BST中的刪除。
其過程如下:
由于B+樹的數據都存儲在葉子結點中,分支結點均為索引,方便掃庫,只需要掃一遍葉子結點即可,但是B樹因為其分支結點同樣存儲著數據,我們要找到具體的數據,需要進行一次中序遍歷按序來掃,所以B+樹更加適合在區間查詢的情況,所以通常B+樹用于數據庫索引,而B樹則常用于文件索引。
同樣的,以一個m階樹為例:
根結點只有一個,分支數量范圍為[2,m];
分支結點,每個結點包含分支數范圍為[ceil(m/2), m];
分支結點的關鍵字數量等于其子分支的數量減一,關鍵字的數量范圍為[ceil(m/2)-1, m-1],關鍵字順序遞增;
所有葉子結點都在同一層;
其操作和B樹的操作是類似的,不過需要注意的是,在增加值的時候,如果存在滿員的情況,將選擇結點中的值作為新的索引,還有在刪除值的時候,索引中的關鍵字并不會刪除,也不會存在父親結點的關鍵字下沉的情況,因為那只是索引。
這都是由于B+樹和B具有這不同的存儲結構所造成的區別,以一個m階樹為例。
關鍵字的數量不同;B+樹中分支結點有m個關鍵字,其葉子結點也有m個,其關鍵字只是起到了一個索引的作用,但是B樹雖然也有m個子結點,但是其只擁有m-1個關鍵字。
存儲的位置不同;B+樹中的數據都存儲在葉子結點上,也就是其所有葉子結點的數據組合起來就是完整的數據,但是B樹的數據存儲在每一個結點中,并不僅僅存儲在葉子結點上。
分支結點的構造不同;B+樹的分支結點僅僅存儲著關鍵字信息和兒子的指針(這里的指針指的是磁盤塊的偏移量),也就是說內部結點僅僅包含著索引信息。
查詢不同;B樹在找到具體的數值以后,則結束,而B+樹則需要通過索引找到葉子結點中的數據才結束,也就是說B+樹的搜索過程中走了一條從根結點到葉子結點的路徑。
目前計算機使用的主存基本都是隨機讀寫存儲器(RAM),現代RAM的結構和存取原理比較復雜,這里本文拋卻具體差別,抽象出一個十分簡單的存取模型來說明RAM的工作原理。
圖5
從抽象角度看,主存是一系列的存儲單元組成的矩陣,每個存儲單元存儲固定大小的數據。每個存儲單元有唯一的地址,現代主存的編址規則比較復雜,這里將其簡化成一個二維地址:通過一個行地址和一個列地址可以唯一定位到一個存儲單元。圖5展示了一個4 x 4的主存模型。
主存的存取過程如下:
當系統需要讀取主存時,則將地址信號放到地址總線上傳給主存,主存讀到地址信號后,解析信號并定位到指定存儲單元,然后將此存儲單元數據放到數據總線上,供其它部件讀取。
寫主存的過程類似,系統將要寫入單元地址和數據分別放在地址總線和數據總線上,主存讀取兩個總線的內容,做相應的寫操作。
這里可以看出,主存存取的時間僅與存取次數呈線性關系,因為不存在機械操作,兩次存取的數據的“距離”不會對時間有任何影響,例如,先取A0再取A1和先取A0再取D3的時間消耗是一樣的。
上文說過,索引一般以文件形式存儲在磁盤上,索引檢索需要磁盤I/O操作。與主存不同,磁盤I/O存在機械運動耗費,因此磁盤I/O的時間消耗是巨大的。
圖6是磁盤的整體結構示意圖。
圖6
一個磁盤由大小相同且同軸的圓形盤片組成,磁盤可以轉動(各個磁盤必須同步轉動)。在磁盤的一側有磁頭支架,磁頭支架固定了一組磁頭,每個磁頭負責存取一個磁盤的內容。磁頭不能轉動,但是可以沿磁盤半徑方向運動(實際是斜切向運動),每個磁頭同一時刻也必須是同軸的,即從正上方向下看,所有磁頭任何時候都是重疊的(不過目前已經有多磁頭獨立技術,可不受此限制)。
圖7是磁盤結構的示意圖。
圖7
盤片被劃分成一系列同心環,圓心是盤片中心,每個同心環叫做一個磁道,所有半徑相同的磁道組成一個柱面。磁道被沿半徑線劃分成一個個小的段,每個段叫做一個扇區,每個扇區是磁盤的最小存儲單元。為了簡單起見,我們下面假設磁盤只有一個盤片和一個磁頭。
當需要從磁盤讀取數據時,系統會將數據邏輯地址傳給磁盤,磁盤的控制電路按照尋址邏輯將邏輯地址翻譯成物理地址,即確定要讀的數據在哪個磁道,哪個扇區。為了讀取這個扇區的數據,需要將磁頭放到這個扇區上方,為了實現這一點,磁頭需要移動對準相應磁道,這個過程叫做尋道,所耗費時間叫做尋道時間,然后磁盤旋轉將目標扇區旋轉到磁頭下,這個過程耗費的時間叫做旋轉時間。
由于存儲介質的特性,磁盤本身存取就比主存慢很多,再加上機械運動耗費,磁盤的存取速度往往是主存的幾百分分之一,因此為了提高效率,要盡量減少磁盤I/O。為了達到這個目的,磁盤往往不是嚴格按需讀取,而是每次都會預讀,即使只需要一個字節,磁盤也會從這個位置開始,順序向后讀取一定長度的數據放入內存。這樣做的理論依據是計算機科學中著名的局部性原理:
當一個數據被用到時,其附近的數據也通常會馬上被使用。
程序運行期間所需要的數據通常比較集中。
由于磁盤順序讀取的效率很高(不需要尋道時間,只需很少的旋轉時間),因此對于具有局部性的程序來說,預讀可以提高I/O效率。
預讀的長度一般為頁(page)的整倍數。頁是計算機管理存儲器的邏輯塊,硬件及操作系統往往將主存和磁盤存儲區分割為連續的大小相等的塊,每個存儲塊稱為一頁(在許多操作系統中,頁得大小通常為4k),主存和磁盤以頁為單位交換數據。當程序要讀取的數據不在主存中時,會觸發一個缺頁異常,此時系統會向磁盤發出讀盤信號,磁盤會找到數據的起始位置并向后連續讀取一頁或幾頁載入內存中,然后異常返回,程序繼續運行。
到這里終于可以分析B-/+Tree索引的性能了。
上文說過一般使用磁盤I/O次數評價索引結構的優劣。先從B-Tree分析,根據B-Tree的定義,可知檢索一次最多需要訪問h個節點。數據庫系統的設計者巧妙利用了磁盤預讀原理,將一個節點的大小設為等于一個頁,這樣每個節點只需要一次I/O就可以完全載入。為了達到這個目的,在實際實現B-Tree還需要使用如下技巧:
每次新建節點時,直接申請一個頁的空間,這樣就保證一個節點物理上也存儲在一個頁里,加之計算機存儲分配都是按頁對齊的,就實現了一個node只需一次I/O。
B-Tree中一次檢索最多需要h-1次I/O(根節點常駐內存),漸進復雜度為O(h)=O(logdN)O(h)=O(logdN)。一般實際應用中,出度d是非常大的數字,通常超過100,因此h非常小(通常不超過3)。
綜上所述,用B-Tree作為索引結構效率是非常高的。
而紅黑樹這種結構,h明顯要深的多。由于邏輯上很近的節點(父子)物理上可能很遠,無法利用局部性,所以紅黑樹的I/O漸進復雜度也為O(h),效率明顯比B-Tree差很多。
上文還說過,B+Tree更適合外存索引,原因和內節點出度d有關。從上面分析可以看到,d越大索引的性能越好,而出度的上限取決于節點內key和data的大小:
dmax=floor(pagesize/(keysize+datasize+pointsize))dmax=floor(pagesize/(keysize+datasize+pointsize))
floor表示向下取整。由于B+Tree內節點去掉了data域,因此可以擁有更大的出度,擁有更好的性能。
這一章從理論角度討論了與索引相關的數據結構與算法問題,下一章將討論B+Tree是如何具體實現為MySQL中索引,同時將結合MyISAM和InnDB存儲引擎介紹非聚集索引和聚集索引兩種不同的索引實現形式。
在MySQL中,索引屬于存儲引擎級別的概念,不同存儲引擎對索引的實現方式是不同的,本文主要討論MyISAM和InnoDB兩個存儲引擎的索引實現方式。
MyISAM引擎使用B+Tree作為索引結構,葉節點的data域存放的是數據記錄的地址。下圖是MyISAM索引的原理圖:
圖8
這里設表一共有三列,假設我們以Col1為主鍵,則圖8是一個MyISAM表的主索引(Primary key)示意。可以看出MyISAM的索引文件僅僅保存數據記錄的地址。在MyISAM中,主索引和輔助索引(Secondary key)在結構上沒有任何區別,只是主索引要求key是唯一的,而輔助索引的key可以重復。如果我們在Col2上建立一個輔助索引,則此索引的結構如下圖所示:
圖9
同樣也是一顆B+Tree,data域保存數據記錄的地址。因此,MyISAM中索引檢索的算法為首先按照B+Tree搜索算法搜索索引,如果指定的Key存在,則取出其data域的值,然后以data域的值為地址,讀取相應數據記錄。
MyISAM的索引方式也叫做“非聚集”的,之所以這么稱呼是為了與InnoDB的聚集索引區分。
雖然InnoDB也使用B+Tree作為索引結構,但具體實現方式卻與MyISAM截然不同。
第一個重大區別是InnoDB的數據文件本身就是索引文件。從上文知道,MyISAM索引文件和數據文件是分離的,索引文件僅保存數據記錄的地址。而在InnoDB中,表數據文件本身就是按B+Tree組織的一個索引結構,這棵樹的葉節點data域保存了完整的數據記錄。這個索引的key是數據表的主鍵,因此InnoDB表數據文件本身就是主索引。
圖10
圖10是InnoDB主索引(同時也是數據文件)的示意圖,可以看到葉節點包含了完整的數據記錄。這種索引叫做聚集索引。因為InnoDB的數據文件本身要按主鍵聚集,所以InnoDB要求表必須有主鍵(MyISAM可以沒有),如果沒有顯式指定,則MySQL系統會自動選擇一個可以唯一標識數據記錄的列作為主鍵,如果不存在這種列,則MySQL自動為InnoDB表生成一個隱含字段作為主鍵,這個字段長度為6個字節,類型為長整形。
第二個與MyISAM索引的不同是InnoDB的輔助索引data域存儲相應記錄主鍵的值而不是地址。換句話說,InnoDB的所有輔助索引都引用主鍵作為data域。例如,圖11為定義在Col3上的一個輔助索引:
圖11
這里以英文字符的ASCII碼作為比較準則。聚集索引這種實現方式使得按主鍵的搜索十分高效,但是輔助索引搜索需要檢索兩遍索引:首先檢索輔助索引獲得主鍵,然后用主鍵到主索引中檢索獲得記錄。
了解不同存儲引擎的索引實現方式對于正確使用和優化索引都非常有幫助,例如知道了InnoDB的索引實現后,就很容易明白為什么不建議使用過長的字段作為主鍵,因為所有輔助索引都引用主索引,過長的主索引會令輔助索引變得過大。再例如,用非單調的字段作為主鍵在InnoDB中不是個好主意,因為InnoDB數據文件本身是一顆B+Tree,非單調的主鍵會造成在插入新記錄時數據文件為了維持B+Tree的特性而頻繁的分裂調整,十分低效,而使用自增字段作為主鍵則是一個很好的選擇。
下一章將具體討論這些與索引有關的優化策略。
B-/+Tree索引的性能優勢: 一般使用磁盤I/O次數評價索引優劣。
1.結合操作系統存儲結構優化處理: mysql巧妙運用操作系統存儲結構(一個節點分配到一個存儲頁中->盡量減少IO次數) & 磁盤預讀(緩存預讀->加速預讀馬上要用到的數據).
2.B+Tree 單個節點能放多個子節點,相同IO次數,檢索出更多信息。
3.B+TREE 只在葉子節點存儲數據 & 所有葉子結點包含一個鏈指針 & 其他內層非葉子節點只存儲索引數據。只利用索引快速定位數據索引范圍,先定位索引再通過索引高效快速定位數據。
詳解:Mysql設計利用了磁盤預讀原理,將一個B+Tree節點大小設為一個頁大小,在新建節點時直接申請一個頁的空間,這樣就能保證一個節點物理上存儲在一個頁里,加之計算機存儲分配都是按頁對齊的,這樣就實現了每個Node節點只需要一次I/O操作。
B-Tree索引、B+Tree索引: 單個節點能放多個子節點,查詢IO次數相同(mysql查詢IO次數最多3-5次-所以需要每個節點需要存儲很多數據)
B+TREE 只在葉子節點存儲數據 & 所有葉子結點包含一個鏈指針 & 其他內層非葉子節點只存儲索引數據。只利用索引快速定位數據索引范圍,先定位索引再通過索引高效快速定位數據。
B+Tree更適合外存索引,原因和內節點出度d有關。從上面分析可以看到,d越大索引的性能越好,而出度的上限取決于節點內key和data的大小:
B+Tree內節點去掉了data域,因此可以擁有更大的出度,擁有更好的性能。只利用索引快速定位數據索引范圍,先定位索引再通過索引高效快速定位數據。
dmax=floor(pagesize/(keysize+datasize+pointsize))
聚簇索引: 索引 和 數據文件為同一個文件。非聚簇索引: 索引 和 數據文件分開的索引。
MyISAM & InnoDB 都使用B+Tree索引結構。但是底層索引存儲不同,MyISAM 采用非聚簇索引,而InnoDB采用聚簇索引。
MyISAM索引原理:采用非聚簇索引-MyISAM myi索引文件和myd數據文件分離,索引文件僅保存數據記錄的指針地址。葉子節點data域存儲指向數據記錄的指針地址。(底層存儲結構: frm -表定義、 myi -myisam索引、 myd-myisam數據)
MyISAM索引按照B+Tree搜索,如果指定的Key存在,則取出其data域的值,然后以data域值-數據指針地址去讀取相應數據記錄。輔助索引和主索引在結構上沒有任何區別,只是主索引要求key是唯一的,而輔助索引的key可以重復。MyISAM索引樹如下:
InnoDB優勢:高擴展性,充分發揮硬件性能、 Crash Safe、 支持事務、 可以在線熱備份
InnoDB特性:
1. 事務支持(ACID)2. 擴展性優良 3. 讀寫不沖突 4. 緩存加速
2. 功能組件: redo/undo & 異步IO & MVCC & 行級別鎖 & Page Cache(LRU)
InnoDB物理存儲結構如下圖:
InnoDB表空間管理
InnoDB物理存儲文件結構說明:
InnoDB以表空間Tablespace(idb文件)結構進行組織,每個Tablespace 包含多個Segment段,每個段(分為2種段:葉子節點Segment&非葉子節點Segment), 一個Segment段包含多個Extent,一個Extent占用1M空間包含64個Page(每個Page 16k),InnoDB B-Tree 一個邏輯節點就分配一個物理Page,一個節點一次IO操作。,一個Page里包含很多有序數據Row行數據,Row行數據中包含Filed屬性數據等信息。
? 表空間(ibd文件)
? 段(一個索引2段:葉子節點Segment & 非葉子節點Segment)
? Extent(1MB):一個Extent(1M) 包含64個 Page(16k),一個Page里包含很多有序行數據 , InnoDB B-Tree 一個邏輯節點就分配一個物理Page,一個節點一次IO操作。
? Page(16KB)
? Row
? Field
表插入數據擴展原理: 一次擴張一個Extent空間(1M),64個Page,按照順序結構向每個page中插入順序。
InnoDB邏輯組織結構:
InnoDB索引樹結構
每個索引一個B+樹, 一個B+樹節點 = 一個物理Page(16K)
? 數據按16KB切片為Page 并編號, 編號可映射到物理文件偏移(16K * N), B+樹葉子節點前后形成雙向鏈表, 數據按主鍵索引聚簇, 二級索引葉節點存儲主鍵值, 通過葉節點主鍵值回表查找數據。
采用聚簇索引- InnoDB數據&索引文件為一個idb文件,表數據文件本身就是主索引,相鄰的索引臨近存儲。 葉節點data域保存了完整的數據記錄(數據[除主鍵id外其他列data]+主索引[索引key:表主鍵id])。 葉子節點直接存儲數據記錄,以主鍵id為key,葉子節點中直接存儲數據記錄。(底層存儲結構: frm -表定義、 ibd: innoDB數據&索引文件)
注:由于InnoDB采用聚簇索引結構存儲,索引InnoDB的數據文件需要按照主鍵聚集,因此InnoDB要求表必須有主鍵(MyISAM可以沒有)。如果沒有指定mysql會自動選擇一個可以唯一表示數據記錄的列作為主鍵,如果不存在這樣的列,mysql自動為InnoDB表生成一個隱含字段(6個字節長整型)作為主鍵。 InnoDB的所有 輔助索引 都引用 數據記錄的主鍵 作為data域。
聚簇索引這種實現方式使得按主鍵的搜索十分高效,但是輔助索引搜索需要檢索兩遍索引:首先檢索輔助索引獲得數據記錄主鍵,然后用主鍵到主索引中檢索獲得數據記錄。InnoDB聚簇索引結構:
#1.索引精確查找: 確定定位條件, 找到根節點Page No, 根節點讀到內存, 逐層向下查找, 讀取葉子節點Page,通過 二分查找找到記錄或未命中。(select * from user_info where id = 23)
#2.索引范圍查找:讀取根節點至內存, 確定索引定位條件id=18, 找到滿足條件第一個葉節點
, 順序掃描所有結果, 直到終止條件滿足id >=22 (select * from user_info where id >= 18 and id < 22)
#3.全表掃描:直接讀取葉節點頭結點, 順序掃描, 返回符合條件記錄, 到最終節點結束
(select * from user_info where name = 'abc')
#4.二級索引查找
Create table table_x(int id primary key, varchar(64) name,key sec_index(name) )
? Select * from table_x where name = “d”;
通過二級索引查出對應主鍵,拿主鍵回表查主鍵索引得到數據, 二級索引可篩選掉大量無效記錄,提高效率
以上是“Mysql索引實現原理的示例分析”這篇文章的所有內容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內容對大家有所幫助,如果還想學習更多知識,歡迎關注億速云行業資訊頻道!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。