1. 程式人生 > >mysql索引原理剖析

mysql索引原理剖析

mage add 獲得 旋轉速度 tran 運行期 多個 pla 線性

一、索引的原理

  所謂索引,即是快速定位與查找,那麽索引的結構組織要盡量減少查找過程中磁盤I/O的存取次數(B+樹相比B樹,其非葉子節點占用更小的空間,可以有更多非葉子節點存放在再內存中,減少大量的IO)

  1、索引原理

  2、局部性原理和磁盤預讀

  局部性原理:

  • 當一個數據被用到時,其附近的數據也通常會馬上被使用。
  • 程序運行期間所需要的數據通常比較集中。

  由於磁盤順序讀取的效率很高(不需要尋道時間,只需很少的旋轉時間),因此對於具有局部性的程序來說,預讀可以提高I/O效率。

  預讀的長度一般為頁(page)的整倍數。頁是計算機管理存儲器的邏輯塊,硬件及操作系統往往將主存和磁盤存儲區分割為連續的大小相等的塊,每個存儲塊稱為一頁(在許多操作系統中,頁得大小通常為4k),主存和磁盤以頁為單位交換數據。當程序要讀取的數據不在主存中時,會觸發一個缺頁異常,此時系統會向磁盤發出讀盤信號,磁盤會找到數據的起始位置並向後連續讀取一頁或幾頁載入內存中,然後異常返回,程序繼續運行。

  數據庫系統巧妙利用了磁盤預讀原理,將一個節點的大小設為等於一個頁,這樣每個節點只需要一次I/O就可以完全載入。

二、索引的數據結構

  大規模數據存儲中,實現索引查詢這樣一個實際背景下,樹節點存儲的元素數量是有限的(如果元素數量非常多的話,查找就退化成節點內部的線性查找了),這樣導致二叉查找樹結構由於樹的深度過大而造成磁盤I/O讀寫過於頻繁,進而導致查詢效率低下(為什麽會出現這種情況,待會在外部存儲器-磁盤中有所解釋),那麽如何減少樹的深度(當然是不能減少查詢的數據量),一個基本的想法就是:采用多叉樹結構(由於樹節點元素數量是有限的,自然該節點的子樹數量也就是有限的)。

  也就是說,因為磁盤的操作費時費資源,如果過於頻繁的多次查找勢必效率低下。那麽如何提高效率,即如何避免磁盤過於頻繁的多次查找呢?根據磁盤查找存取的次數往往由樹的高度所決定,所以,只要我們通過某種較好的樹結構減少樹的結構盡量減少樹的高度,那麽是不是便能有效減少磁盤查找存取的次數呢?那這種有效的樹結構是一種怎樣的樹呢?

  這樣我們就提出了一個新的查找樹結構——多路查找樹。根據平衡二叉樹的啟發,自然就想到平衡多路查找樹結構,也就是這篇文章所要闡述的第一個主題B~tree,即B樹結構(後面,我們將看到,B樹的各種操作能使B樹保持較低的高度,從而達到有效避免磁盤過於頻繁的查找存取操作,從而有效提高查找效率)。

  1.外存-磁盤

  計算機存儲設備一般分為兩種:內存儲器(main memory)和外存儲器(external memory)。 內存存取速度快,但容量小,價格昂貴,而且不能長期保存數據(在不通電情況下數據會消失)。

  外存儲器—磁盤是一種直接存取的存儲設備(DASD)。它是以存取時間變化不大為特征的。可以直接存取任何字符組,且容量大、速度較其它外存設備更快。

  當磁盤驅動器執行讀/寫功能時。盤片裝在一個主軸上,並繞主軸高速旋轉,當磁道在讀/寫頭(又叫磁頭) 下通過時,就可以進行數據的讀 / 寫了。

  磁盤上數據必須用一個三維地址唯一標示:柱面號、盤面號、塊號(磁道上的盤塊)。

  讀/寫磁盤上某一指定數據需要下面3個步驟:

  (1) 首先移動臂根據柱面號使磁頭移動到所需要的柱面上,這一過程被稱為定位或查找 。

  (2) 如上圖11.3中所示的6盤組示意圖中,所有磁頭都定位到了10個盤面的10條磁道上(磁頭都是雙向的)。這時根據盤面號來確定指定盤面上的磁道。

  (3) 盤面確定以後,盤片開始旋轉,將指定塊號的磁道段移動至磁頭下。

  經過上面三個步驟,指定數據的存儲位置就被找到。這時就可以開始讀/寫操作了。

  訪問某一具體信息,由3部分時間組成:

  ● 查找時間(seek time) Ts: 完成上述步驟(1)所需要的時間。這部分時間代價最高,最大可達到0.1s左右。

  ● 等待時間(latency time) Tl: 完成上述步驟(3)所需要的時間。由於盤片繞主軸旋轉速度很快,一般為7200轉/分(電腦硬盤的性能指標之一, 家用的普通硬盤的轉速一般有5400rpm(筆記本)、7200rpm幾種)。因此一般旋轉一圈大約0.0083s。

  ● 傳輸時間(transmission time) Tt: 數據通過系統總線傳送到內存的時間,一般傳輸一個字節(byte)大概0.02us=2*10^(-8)s

  磁盤讀取數據是以盤塊(block)為基本單位的。位於同一盤塊中的所有數據都能被一次性全部讀取出來。而磁盤IO代價主要花費在查找時間Ts上。因此我們應該盡量將相關信息存放在同一盤塊,同一磁道中。或者至少放在同一柱面或相鄰柱面上,以求在讀/寫信息時盡量減少磁頭來回移動的次數,避免過多的查找時間Ts

  所以,在大規模數據存儲方面,大量數據存儲在外存磁盤中,而在外存磁盤中讀取/寫入塊(block)中某數據時,首先需要定位到磁盤中的某塊,如何有效地查找磁盤中的數據,需要一種合理高效的外存數據結構,就是下面所要重點闡述的B-tree結構,以及相關的變種結構:B+-tree結構和B*-tree結構。

  1、B樹

  B-樹,即為B樹。因為B樹的原英文名稱為B-tree,而國內很多人喜歡把B-tree譯作B-樹,其實,這是個非常不好的直譯,很容易讓人產生誤解。如人們可能會以為B-樹是一種樹,而B樹又是一種一種樹。而事實上是,B-tree就是指的B樹

  B 樹是為了磁盤或其它存儲設備而設計的一種多叉(下面你會看到,相對於二叉,B樹每個內結點有多個分支,即多叉)平衡查找樹。

  B樹與紅黑樹最大的不同在於,B樹的結點可以有許多子女,從幾個到幾千個。那為什麽又說B樹與紅黑樹很相似呢?因為與紅黑樹一樣,一棵含n個結點的B樹的高度也為O(lgn),但可能比一棵紅黑樹的高度小許多,應為它的分支因子比較大。所以,B樹可以在O(logn)時間內,實現各種如插入(insert),刪除(delete)等動態集合操作。

  如下圖所示,即是一棵B樹,一棵關鍵字為英語中輔音字母的B樹,現在要從樹種查找字母R(包含n[x]個關鍵字的內結點x,x有n[x]+1]個子女(也就是說,一個內結點x若含有n[x]個關鍵字,那麽x將含有n[x]+1個子女)。所有的葉結點都處於相同的深度,帶陰影的結點為查找字母R時要檢查的結點):

      技術分享圖片

  相信,從上圖你能輕易的看到,一個內結點x若含有n[x]個關鍵字,那麽x將含有n[x]+1個子女。如含有2個關鍵字D H的內結點有3個子女,而含有3個關鍵字Q T X的內結點有4個子女。

  B 樹又叫平衡多路查找樹。

  1. 樹中每個結點最多含有m個孩子(m>=2);
  2. 除根結點和葉子結點外,其它每個結點至少有[ceil(m / 2)]個孩子(其中ceil(x)是一個取上限的函數);
  3. 若根結點不是葉子結點,則至少有2個孩子(特殊情況:沒有孩子的根結點,即根結點為葉子結點,整棵樹只有一個根節點);
  4. 所有葉子結點都出現在同一層,葉子結點不包含任何關鍵字信息
  5. 每個非終端結點中包含有n個關鍵字信息: (n,P0,K1,P1,K2,P2,......,Kn,Pn)。其中:
    a) Ki (i=1...n)為關鍵字,且關鍵字按順序升序排序K(i-1)< Ki。
    b) Pi為指向子樹根的接點,且指針P(i-1)指向子樹種所有結點的關鍵字均小於Ki,但都大於K(i-1)。
    c) 關鍵字的個數n必須滿足: [ceil(m / 2)-1]<= n <= m-1。

  B樹中的每個結點根據實際情況可以包含大量的關鍵字信息和分支(當然是不能超過磁盤塊的大小,根據磁盤驅動(disk drives)的不同,一般塊的大小在1k~4k左右);這樣樹的深度降低了,這就意味著查找一個元素只要很少結點從外存磁盤中讀入內存,很快訪問到要查找的數據。

        技術分享圖片

  文件查找的具體過程

  為了簡單,這裏用少量數據構造一棵3叉樹的形式,實際應用中的B樹結點中關鍵字很多的。上面的圖中比如根結點,其中17表示一個磁盤文件的文件名;小紅方塊表示這個17文件內容在硬盤中的存儲位置;p1表示指向17左子樹的指針。

  其結構可以簡單定義為:

技術分享圖片
typedef struct {
    /*文件數*/
    int  file_num;
    /*文件名(key)*/
    char * file_name[max_file_num];
    /*指向子節點的指針*/
     BTNode * BTptr[max_file_num+1];
     /*文件在硬盤中的存儲位置*/
     FILE_HARD_ADDR offset[max_file_num];
}BTNode;
View Code

  假如每個盤塊可以正好存放一個B樹的結點(正好存放2個文件名)。那麽一個BTNODE結點就代表一個盤塊,而子樹指針就是存放另外一個盤塊的地址。

  下面,咱們來模擬下查找文件29的過程:

  1. 根據根結點指針找到文件目錄的根磁盤塊1,將其中的信息導入內存。【磁盤IO操作 1次】
  2. 此時內存中有兩個文件名17、35和三個存儲其他磁盤頁面地址的數據。根據算法我們發現:17<29<35,因此我們找到指針p2。
  3. 根據p2指針,我們定位到磁盤塊3,並將其中的信息導入內存。【磁盤IO操作 2次】
  4. 此時內存中有兩個文件名26,30和三個存儲其他磁盤頁面地址的數據。根據算法我們發現:26<29<30,因此我們找到指針p2。
  5. 根據p2指針,我們定位到磁盤塊8,並將其中的信息導入內存。【磁盤IO操作 3次】
  6. 此時內存中有兩個文件名28,29。根據算法我們查找到文件名29,並定位了該文件內存的磁盤地址。

  分析上面的過程,發現需要3次磁盤IO操作和3次內存查找操作。關於內存中的文件名查找,由於是一個有序表結構,可以利用折半查找提高效率。至於IO操作是影響整個B樹查找效率的決定因素。

  當然,如果我們使用平衡二叉樹的磁盤存儲結構來進行查找,磁盤4次,最多5次,而且文件越多,B樹比平衡二叉樹所用的磁盤IO操作次數將越少,效率也越高。

  樹的高度:

  根據上面的例子我們可以看出,對於輔存做IO讀的次數取決於B樹的高度。而B樹的高度由什麽決定的呢?若B樹某一非葉子節點包含N個關鍵字,則此非葉子節點含有N+1個孩子結點,而所有的葉子結點都在第I層,我們可以得出:

  1. 因為根至少有兩個孩子,因此第2層至少有兩個結點。
  2. 除根和葉子外,其它結點至少有┌m/2┐個孩子,
  3. 因此在第3層至少有2*┌m/2┐個結點,
  4. 在第4層至少有2*(┌m/2┐^2)個結點,
  5. 在第 I 層至少有2*(┌m/2┐^(l-2) )個結點,於是有: N+1 ≥ 2*┌m/2┐I-2;
  6. 考慮第L層的結點個數為N+1,那麽2*(┌m/2┐^(l-2))≤N+1,也就是L層的最少結點數剛好達到N+1個,即: I≤ log┌m/2┐((N+1)/2 )+2;
  所以當B樹包含N個關鍵字時,B樹的最大高度為l-1(因為計算B樹高度時,葉結點所在層不計算在內),即:l - 1 = log┌m/2┐((N+1)/2 )+1

  樹中每個結點含有最多含有m個孩子,即m滿足:ceil(m/2)<=m<=m。而樹中每個結點含孩子數越少,樹的高度則越大,故如此

  2、B+樹

    B+-tree:是應文件系統所需而產生的一種B-tree的變形樹。

    一棵m階的B+樹和m階的B樹的異同點在於:

      所有的葉子結點中包含了全部關鍵字的信息,及指向含有這些關鍵字記錄的指針,且葉子結點本身依關鍵字的大小自小而大的順序鏈接。 (而B 樹的葉子節點並沒有包括全部需要查找的信息)

      所有的非終端結點可以看成是索引部分,結點中僅含有其子樹根結點中最大(或最小)關鍵字。 (而B 樹的非終節點也包含需要查找的有效信息)

      技術分享圖片

  3、B樹與B+樹區別

  關鍵字的數量不同;B+樹中分支結點有m個關鍵字,其葉子結點也有m個,其關鍵字只是起到了一個索引的作用,但是B樹雖然也有m個子結點,但是其只擁有m-1個關鍵字。

  存儲的位置不同;B+樹中的數據都存儲在葉子結點上,也就是其所有葉子結點的數據組合起來就是完整的數據,但是B樹的數據存儲在每一個結點中,並不僅僅存儲在葉子結點上。

  分支結點的構造不同;B+樹的分支結點僅僅存儲著關鍵字信息和兒子的指針(這裏的指針指的是磁盤塊的偏移量),也就是說內部結點僅僅包含著索引信息。

  查詢不同;B樹在找到具體的數值以後,則結束,而B+樹則需要通過索引找到葉子結點中的數據才結束,也就是說B+樹的搜索過程中走了一條從根結點到葉子結點的路徑。

三、為什麽Mysql使用B+樹作索引,而不使用B樹?

  結構上:

  • B樹中關鍵字集合分布在整棵樹中,葉節點中不包含任何關鍵字信息,而B+樹關鍵字集合分布在葉子結點中,非葉節點只是葉子結點中關鍵字的索引;
  • B樹中任何一個關鍵字只出現在一個結點中,而B+樹中的關鍵字必須出現在葉節點中,也可能在非葉結點中重復出現;

  性能上:

  • 不同於B樹只適合隨機檢索,B+樹同時支持隨機檢索和順序檢索
  • B+樹的磁盤讀寫代價更低。B+樹的內部結點並沒有指向關鍵字具體信息的指針,其內部結點比B樹小,盤塊能容納的結點中關鍵字數量更多,一次性讀入內存中可以查找的關鍵字也就越多,相對的,IO讀寫次數也就降低了。而IO讀寫次數是影響索引檢索效率的最大因素。(舉個例子,假設磁盤中的一個盤塊容納16bytes,而一個關鍵字2bytes,一個關鍵字具體信息指針2bytes。一棵9階B-tree(一個結點最多8個關鍵字)的內部結點需要2個盤快。而B+ 樹內部結點只需要1個盤快。當需要把內部結點讀入內存中的時候,B 樹就比B+ 樹多一次盤塊查找時間(在磁盤中就是盤片旋轉的時間))
  • B+樹的查詢效率更加穩定。B樹搜索有可能會在非葉子結點結束,越靠近根節點的記錄查找時間越短,只要找到關鍵字即可確定記錄的存在,其性能等價於在關鍵字全集內做一次二分查找。而在B+樹中,順序檢索比較明顯,隨機檢索時,任何關鍵字的查找都必須走一條從根節點到葉節點的路,所有關鍵字的查找路徑長度相同,導致每一個關鍵字的查詢效率相當。(由於非終結點並不是最終指向文件內容的結點,而只是葉子結點中關鍵字的索引。所以任何關鍵字的查找必須走一條從根結點到葉子結點的路。所有關鍵字查詢的路徑長度相同,導致每一個數據的查詢效率相當)
  • (數據庫索引采用B+樹的主要原因是,)B-樹在提高了磁盤IO性能的同時並沒有解決元素遍歷的效率低下的問題。B+樹的葉子節點使用指針順序連接在一起,只要遍歷葉子節點就可以實現整棵樹的遍歷。而且在數據庫中基於範圍的查詢是非常頻繁的,而B樹不支持這樣的操作(或者說效率太低)。

  原因:

   (1)B+樹空間利用率更高,可減少I/O次數

     一般來說,索引本身也很大,不可能全部存儲在內存中,因此索引往往以索引文件的形式存儲的磁盤上。這樣的話,索引查找過程中就要產生磁盤I/O消耗。而因為B+樹的內部節點只是作為索引使用,而不像B-樹那樣每個節點都需要存儲硬盤指針。
也就是說:B+樹中每個非葉節點沒有指向某個關鍵字具體信息的指針,所以每一個節點可以存放更多的關鍵字數量,即一次性讀入內存所需要查找的關鍵字也就越多,減少了I/O操作。
   e.g.假設磁盤中的一個盤塊容納16bytes,而一個關鍵字2bytes,一個關鍵字具體信息指針2bytes。一棵9階B-tree(一個結點最多8個關鍵字)的內 部結點需要2個盤快。而B+ 樹內部結點只需要1個盤快。當需要把內部結點讀入內存中的時候,B 樹就比B+ 樹多一次盤塊查找時間(在磁盤中就是盤片旋轉的時間)。

   (2)增刪文件(節點)時,效率更高,
因為B+樹的葉子節點包含所有關鍵字,並以有序的鏈表結構存儲,這樣可很好提高增刪效率。
  (3)B+樹的查詢效率更加穩定,
因為B+樹的每次查詢過程中,都需要遍歷從根節點到葉子節點的某條路徑。所有關鍵字的查詢路徑長度相同,導致每一次查詢的效率相當。

  B+樹還有一個最大的好處,方便掃庫,B樹必須用中序遍歷的方法按序掃庫,而B+樹直接從葉子結點挨個掃一遍就完了,B+樹支持range-query非常方便,而B樹不支持。這是數據庫選用B+樹的最主要原因

  B-樹和B+樹最重要的一個區別就是B+樹只有葉節點存放數據,其余節點用來索引,而B-樹是每個索引節點都會有Data域。這就決定了B+樹更適合用來存儲外部數據,也就是所謂的磁盤數據。

  從Mysql(Inoodb)的角度來看,B+樹是用來充當索引的,一般來說索引非常大,尤其是關系性數據庫這種數據量大的索引能達到億級別,所以為了減少內存的占用,索引也會被存儲在磁盤上。那麽Mysql如何衡量查詢效率呢?磁盤IO次數,B-樹(B類樹)的特定就是每層節點數目非常多,層數很少,目的就是為了就少磁盤IO次數,當查詢數據的時候,最好的情況就是很快找到目標索引,然後讀取數據,使用B+樹就能很好的完成這個目的,但是B-樹的每個節點都有data域(指針),這無疑增大了節點大小,說白了增加了磁盤IO次數(磁盤IO一次讀出的數據量大小是固定的,單個數據變大,每次讀出的就少,IO次數增多,一次IO多耗時啊!),而B+樹除了葉子節點其它節點並不存儲數據,節點小,磁盤IO次數就少。這是優點之一。

  另一個優點是什麽,B+樹所有的Data域在葉子節點,一般來說都會進行一個優化,就是將所有的葉子節點用指針串起來。這樣遍歷葉子節點就能獲得全部數據,這樣就能進行區間訪問啦。

  

本文主要參考:http://blog.csdn.net/v_JULY_v/article/details/6530142/

mysql索引原理剖析