MySQL索引底層實現原理

來源:互聯網
上載者:User

標籤:隨機   原因   col   ODB   基礎   也會   地方   取數   static   

優秀博文:

  1. MySQL索引背後的資料結構及演算法原理
  2. B樹、B-樹、B+樹、B*樹【轉】,mysql索引
  3. MySQL 和 B 樹的那些事
索引的本質

MySQL官方對索引的定義為:索引(Index)是協助MySQL高效擷取資料的資料結構。提取句子主幹,就可以得到索引的本質:索引是資料結構。

我們知道,資料庫查詢是資料庫的最主要功能之一。我們都希望查詢資料的速度能儘可能的快,因此資料庫系統的設計者會從查詢演算法的角度進行最佳化。最基本的查詢演算法當然是順序尋找(linear search),這種複雜度為O(n)的演算法在資料量很大時顯然是糟糕的,好在電腦科學的發展提供了很多更優秀的尋找演算法,例如二分尋找(binary search)、二叉樹尋找(binary tree search)等。如果稍微分析一下會發現,每種尋找演算法都只能應用於特定的資料結構之上,例如二分尋找要求被檢索資料有序,而二叉樹尋找只能應用於二叉尋找樹上,但是資料本身的組織圖不可能完全滿足各種資料結構(例如,理論上不可能同時將兩列都按順序進行組織),所以,在資料之外,資料庫系統還維護著滿足特定尋找演算法的資料結構,這些資料結構以某種方式引用(指向)資料,這樣就可以在這些資料結構上實現進階尋找演算法。這種資料結構,就是索引。

看一個例子:

展示了一種可能的索引方式。左邊是資料表,一共有兩列七條記錄,最左邊的是資料記錄的物理地址(注意邏輯上相鄰的記錄在磁碟上也並不是一定物理相鄰的)。為了加快Col2的尋找,可以維護一個右邊所示的二叉尋找樹,每個節點分別包含索引索引值和一個指向對應資料記錄物理地址的指標,這樣就可以運用二叉尋找在\(O(log_2^n)\)的複雜度內擷取到相應資料。

雖然這是一個貨真價實的索引,但是實際的資料庫系統幾乎沒有使用二叉尋找樹或其進化品種紅/黑樹狀結構(red-black tree)實現的,原因會在下文介紹。

二叉排序樹

在介紹B樹之前,先來看另一棵神奇的樹——二叉排序樹(Binary Sort Tree),首先它是一棵樹,“二叉”這個描述已經很明顯了,就是樹上的一根樹枝開兩個叉,於是遞迴下來就是二叉樹了(所示),而這棵樹上的節點是已經排好序的,具體的定序如下:

  • 若左子樹不空,則左子樹上所有節點的值均小於它的根節點的值
  • 若右子樹不空,則右字數上所有節點的值均大於它的根節點的值
  • 它的左、右子樹也分別為二叉排序數(遞迴定義)

可以看出,二叉排序樹組織資料時,用於尋找是比較方便的,因為每次經過一次節點時,最多可以減少一半的可能,不過極端情況會出現所有節點都位於同一側,直觀上看就是一條直線,那麼這種查詢的效率就比較低了,因此需要對二叉樹左右子樹的高度進行平衡化處理,於是就有了平衡二叉樹(Balenced Binary Tree)。

所謂“平衡”,說的是這棵樹的各個分支的高度是均勻的,它的左子樹和右子樹的高度之差絕對值小於1,這樣就不會出現一條支路特別長的情況。於是,在這樣的平衡樹中進行尋找時,總共比較節點的次數不超過樹的高度,這就確保了查詢的效率(時間複雜度為O(logn))

B樹

還是直接看圖比較清楚,圖中所示,B樹事實上是一種平衡的多叉尋找樹,也就是說最多可以開m個叉(m>=2),我們稱之為m階b樹,為了體現本部落格的良心之處,不同於其他地方都能看到2階B樹,這裡特意畫了一棵5階B樹 。

總的來說,m階B樹滿足以下條件:

  • 每個節點至多可以擁有m棵子樹。
  • 根節點,只有至少有2個節點(要麼極端情況,就是一棵樹就一個根節點,單細胞生物,即是根,也是葉,也是樹)。
  • 非根非葉的節點至少有的Ceil(m/2)個子樹(Ceil表示向上取整,圖中5階B樹,每個節點至少有3個子樹,也就是至少有3個叉)。
  • 非分葉節點中的資訊包括[n,A0,K1,A1,K2,A2,…,Kn,An],,其中n表示該節點中儲存的關鍵字個數,K為關鍵字且Ki<Ki+1,A為指向子樹根節點的指標。
  • 從根到葉子的每一條路徑都有相同的長度,也就是說,葉子節在相同的層,並且這些節點不帶資訊,實際上這些節點就表示找不到指定的值,也就是指向這些節點的指標為空白。

B樹的查詢過程和二叉排序樹比較類似,從根節點依次比較每個結點,因為每個節點中的關鍵字和左右子樹都是有序的,所以只要比較節點中的關鍵字,或者沿著指標就能很快地找到指定的關鍵字,如果尋找失敗,則會返回葉子節點,即null 指標。

例如查詢圖中字母表中的K:

  1. 從根節點P開始,K的位置在P之前,進入左側指標。
  2. 左子樹中,依次比較C、F、J、M,發現K在J和M之間。
  3. 沿著J和M之間的指標,繼續訪問子樹,並依次進行比較,發現第一個關鍵字K即為指定尋找的值。

B樹搜尋的簡單偽演算法如下:

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樹的特點可以總結為如下:

  1. 關鍵字集合分布在整顆樹中。
  2. 任何一個關鍵字出現且只出現在一個節點中。
  3. 搜尋有可能在非葉子節點結束。
  4. 其搜尋效能等價於在關鍵字集合內做一次二分尋找。
  5. B樹在插入刪除新的資料記錄會破壞B-Tree的性質,因為在插入刪除時,需要對樹進行一個分裂、合并、轉移等操作以保持B-Tree性質。
Plus版 — B+樹

作為B樹的加強版,B+樹與B樹的差異在於

  • 有n棵子樹的節點含有n個關鍵字(也有認為是n-1個關鍵字)。
  • 所有的關鍵字全部儲存在葉子節點上,且葉子節點本身根據關鍵字自小而大順序串連。
  • 非葉子節點可以看成索引部分,節點中僅含有其子樹(根節點)中的最大(或最小)關鍵字。

B+樹的尋找過程,與B樹類似,只不過尋找時,如果在非葉子節點上的關鍵字等於給定值,並不終止,而是繼續沿著指標直到葉子節點位置。因此在B+樹,不管尋找成功與否,每次尋找都是走了一條從根到葉子節點的路徑。

B+樹的特性如下:

  • 所有關鍵字都儲存在葉子節上,且鏈表中的關鍵字恰好是有序的。
  • 不可能非葉子節點命中返回。
  • 非葉子節點相當於葉子節點的索引,葉子節點相當於是儲存(關鍵字)資料的資料層。
  • 更適合檔案索引系統。
帶有順序訪問指標的B+Tree

一般在資料庫系統或檔案系統中使用的B+Tree結構都在經典B+Tree的基礎上進行了最佳化,增加了順序訪問指標。

如所示,在B+Tree的每個葉子節點增加一個指向相鄰葉子節點的指標,就形成了帶有順序訪問指標的B+Tree。做這個最佳化的目的是為了提高區間訪問的效能,例4中如果要查詢key為從18到49的所有資料記錄,當找到18後,只需順著節點和指標順序遍曆就可以一次性訪問到所有資料節點,極大提到了區間查詢效率。

MySQL為什麼使用B樹(B+樹)

紅/黑樹狀結構等資料結構也可以用來實現索引,但是檔案系統以及資料庫系統普遍採用B樹或者B+樹,這一節將結合電腦群組成原理相關知識討論B-/+Tree作為索引的理論基礎。

一般來說,索引本身也很大,不可能全部儲存在記憶體中,因此索引往往以索引檔案的形式儲存在磁碟上。這樣的話,索引尋找過程中就要產生磁碟I/O消耗,相對於記憶體存取,I/O存取的消耗要高几個數量級,所以評價一個資料結構作為索引的優劣最重要的指標就是在尋找過程中磁碟I/O操作次數的漸進複雜度。換句話說,索引的結構組織要盡量減少尋找過程中磁碟I/O的存取次數。下面先介紹記憶體和磁碟存取原理,然後再結合這些原理分析B-/+Tree作為索引的效率。

主存存取原理

目前電腦使用的主存基本都是隨機讀寫儲存空間(RAM),現代RAM的結構和存取原理比較複雜,這裡本文拋卻具體差別,抽象出一個十分簡單的存模數型來說明RAM的工作原理。

從抽象角度看,主存是一系列的儲存單元組成的矩陣,每個儲存單中繼存放區固定大小的資料。每個儲存單元有唯一的地址,現代主存的編址規則比較複雜,這裡將其簡化成一個二維地址:通過一個行地址和一個列地址可以唯一定位到一個儲存單元。展示了一個4 x 4的主存模型。

主存的存取過程如下:

當系統需要讀取主存時,則將地址訊號放到地址匯流排上傳給主存,主存讀到地址訊號後,解析訊號並定位到指定儲存單元,然後將此儲存單中繼資料放到資料匯流排上,供其它組件讀取。

寫主存的過程類似,系統將要寫入單元地址和資料分別放在地址匯流排和資料匯流排上,主存讀取兩個匯流排的內容,做相應的寫操作。

這裡可以看出,主存存取的時間僅與存取次數呈線性關係,因為不存在機械操作,兩次存取的資料的“距離”不會對時間有任何影響,例如,先取A0再取A1和先取A0再取D3的時間消耗是一樣的。

磁碟存取原理

上文說過,索引一般以檔案形式儲存在磁碟上,索引檢索需要磁碟I/O操作。與主存不同,磁碟I/O存在機械運動耗費,因此磁碟I/O的時間消耗是巨大的。

是磁碟的整體結構:

一個磁碟由大小相同且同軸的圓形碟片組成,磁碟可以轉動(各個磁碟必須同步轉動)。在磁碟的一側有磁頭支架,磁頭支架固定了一組磁頭,每個磁頭負責存取一個磁碟的內容。磁頭不能轉動,但是可以沿磁碟半徑方向運動(實際是斜切向運動),每個磁頭同一時刻也必須是同軸的,即從正上方向下看,所有磁頭任何時候都是重疊的(不過目前已經有多磁頭獨立技術,可不受此限制)。

是磁碟結構的:

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

當需要從磁碟讀取資料時,系統會將資料邏輯地址傳給磁碟,磁碟的控制電路按照定址邏輯將邏輯地址翻譯成物理地址,即確定要讀的資料在哪個磁軌,哪個扇區。為了讀取這個扇區的資料,需要將磁頭放到這個扇區上方,為了實現這一點,磁頭需要移動對準相應磁軌,這個過程叫做尋道,所耗費時間叫做尋道時間,然後磁碟旋轉將目標扇區旋轉到磁頭下,這個過程耗費的時間叫做旋轉時間。

局部性原理與磁碟預讀

由於儲存介質的特性,磁碟本身存取就比主存慢很多,再加上機械運動耗費,磁碟的存取速度往往是主存的幾百分分之一,因此為了提高效率,要盡量減少磁碟I/O。為了達到這個目的,磁碟往往不是嚴格按需讀取,而是每次都會預讀,即使只需要一個位元組,磁碟也會從這個位置開始,順序向後讀取一定長度的資料放入記憶體。這樣做的理論依據是電腦科學中著名的局部性原理:

當一個資料被用到時,其附近的資料也通常會馬上被使用。

所以,程式運行期間所需要的資料通常應當比較集中。

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

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

B-/+Tree索引的效能分析

到這裡終於可以分析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(log_dN)\)。一般實際應用中,出度d是非常大的數字,通常超過100,因此h非常小(通常不超過3)。(h表示樹的高度 & 出度d表示的是樹的度,即樹中各個節點的度的最大值)

綜上所述,用B-Tree作為索引結構效率是非常高的。

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

上文還說過,B+Tree更適合外存索引,原因和內節點出度d有關。從上面分析可以看到,d越大索引的效能越好,而出度的上限取決於節點內key和data的大小:

\[ d_{max}=floor(pagesize/(keysize+datasize+pointsize))\]

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

還是沒有明白為啥B+樹 效能好!!!

MySQL索引實現

在MySQL中,索引屬於儲存引擎層級的概念,不同儲存引擎對索引的實現方式是不同的,本文主要討論MyISAM和InnoDB兩個儲存引擎的索引實現方式。

MyISAM索引實現

MyISAM引擎使用B+Tree作為索引結構,分葉節點的data域存放的是資料記錄的地址。是MyISAM索引的原理圖:

這裡設表一共有三列,假設我們以Col1為主鍵,則是一個MyISAM表的主索引(Primary key)示意。可以看出MyISAM的索引檔案僅僅儲存資料記錄的地址。在MyISAM中,主索引和輔助索引(Secondary key)在結構上沒有任何區別,只是主索引要求key是唯一的,而輔助索引的key可以重複。如果我們在Col2上建立一個輔助索引,則此索引的結構如所示:

同樣也是一棵B+樹,data域儲存資料記錄的地址。因此,MyISAM中索引檢索的演算法為首先按照B+Tree搜尋演算法搜尋索引,如果指定的Key存在,則取出其data域的值,然後以data域的值為地址,讀取相應資料記錄。

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

InnoDB索引實現

雖然InnoDB也使用B+Tree作為索引結構,但具體實現方式卻與MyISAM截然不同。

第一個重大區別是InnoDB的資料檔案本身就是索引檔案。從上文知道,MyISAM索引檔案和資料檔案是分離的,索引檔案僅儲存資料記錄的地址。而在InnoDB中,表資料檔案本身就是按B+Tree組織的一個索引結構,這棵樹的分葉節點data域儲存了完整的資料記錄。這個索引的key是資料表的主鍵,因此InnoDB表資料檔案本身就是主索引。

是InnoDB主索引(同時也是資料檔案)的,可以看到分葉節點包含了完整的資料記錄。這種索引叫做叢集索引。因為InnoDB的資料檔案本身要按主鍵聚集,所以InnoDB要求表必須有主鍵(MyISAM可以沒有),如果沒有顯式指定,則MySQL系統會自動選擇一個可以唯一標識資料記錄的列作為主鍵,如果不存在這種列,則MySQL自動為InnoDB表產生一個隱含欄位作為主鍵,這個欄位長度為6個位元組,類型為長整型。

第二個與MyISAM索引的不同是InnoDB的輔助索引data域儲存相應記錄主鍵的值而不是地址。換句話說,InnoDB的所有輔助索引都引用主鍵作為data域。例如,為定義在Col3上的一個輔助索引:

這裡以英文字元的ASCII碼作為比較準則。叢集索引這種實現方式使得按主鍵的搜尋十分高效,但是輔助索引搜尋需要檢索兩遍索引:首先檢索輔助索引獲得主鍵,然後用主鍵到主索引中檢索獲得記錄。

瞭解不同儲存引擎的索引實現方式對於正確使用和最佳化索引都非常有協助,例如知道了InnoDB的索引實現後,就很容易明白為什麼不建議使用過長的欄位作為主鍵,因為所有輔助索引都引用主索引,過長的主索引會令輔助索引變得過大。再例如,用非單調的欄位作為主鍵在InnoDB中不是個好主意,因為InnoDB資料檔案本身是一棵B+Tree,非單調的主鍵會造成在插入新記錄時資料檔案為了維持B+Tree的特性而頻繁的分裂調整,十分低效,而使用自增欄位作為主鍵則是一個很好的選擇。

MySQL索引底層實現原理

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.