mysql爲什麼用B+樹,innodb和myisam的區別?

  今天給大家分享一篇乾貨,面試必備之Mysql索引底層原理分析

  • Mysql索引的本質

  • Mysql索引的底層原理

  • Mysql索引的實戰經驗

面試

問:數據庫中最常見的慢查詢優化方式是什麼?

同學A:加索引。

問:爲什麼加索引能優化慢查詢?

同學A:...不知道

同學B:因爲索引其實就是一種優化查詢的數據結構,比如Mysql中的索引是用B+樹實現的,而B+樹就是一種數據結構,可以優化查詢速度,可以利用索引快速查找數據,所以能優化查詢。

問:你知道哪些數據結構可以提高查詢速度?(聽到這個問題就感覺此處有坑...)

同學B:哈希表、完全平衡二叉樹、B樹、B+樹等等。

問:那這些數據結構既然都能優化查詢速度,Mysql爲何選擇使用B+樹?

同學B:...不知道

提問

SHOW INDEX FROM employees.titles;

有一個titles表,主鍵由empno,title,fromdate三個字段組成。

那麼以下幾個語句會用到索引嗎?

  1. select * from employees.titles where emp_no=1
  2. select * from employees.titles where title='1'
  3. select * from employees.titles where emp_no='1' and title=1
  4. select * from employees.titles where title='1' and emp_no=1

爲什麼哈希表、完全平衡二叉樹、B樹、B+樹都可以優化查詢,爲何Mysql獨獨喜歡B+樹?

哈希表有什麼特點?

假如有這麼一張表(表名:sanguo):

現在對name字段建立哈希索引:

注意字段值所對應的數組下標是哈希算法隨機算出來的,所以可能出現哈希衝突。那麼對於這樣一個索引結構,現在來執行下面的sql語句: 

select * from sanguo where name='周瑜'

可以直接對‘周瑜’按哈希算法算出來一個數組下標,然後可以直接從數據中取出數據並拿到所對應那一行數據的地址,進而查詢那一行數據。 那麼如果現在執行下面的sql語句:

select * from sanguo where name>'周瑜'

則無能爲力,因爲哈希表的特點就是可以快速的精確查詢,但是不支持範圍查詢

如果用完全平衡二叉樹呢?

還是上面的表數據用完全平衡二叉樹表示如下圖(爲了簡單,數據對應的地址就不畫在圖中了。):

圖中的每一個節點實際上應該有四部分:

  1. 左指針,指向左子樹

  2. 鍵值

  3. 鍵值所對應的數據的存儲地址

  4. 右指針,指向右子樹

另外需要提醒的是,二叉樹是有順序的,簡單的說就是“左邊的小於右邊的”假如我們現在來查找‘周瑜’,需要找2次(第一次曹操,第二次周瑜),比哈希表要多一次。而且由於完全平衡二叉樹是有序的,所以也是支持範圍查找的。

如果用B樹呢?

還是上面的表數據用B樹表示如下圖(爲了簡單,數據對應的地址就不畫在圖中了。):

可以發現同樣的元素,B樹的表示要比完全平衡二叉樹要“矮”,原因在於B樹中的一個節點可以存儲多個元素。

如果用B+樹呢?

還是上面的表數據用B+樹表示如下圖(爲了簡單,數據對應的地址就不畫在圖中了。):

我們可以發現同樣的元素,B+樹的表示要比B樹要“胖”,原因在於B+樹中的非葉子節點會冗餘一份在葉子節點中,並且葉子節點之間用指針相連。

那麼B+樹到底有什麼優勢呢?

這裏我們用“反證法”,假如我們現在就用完全平衡二叉樹作爲索引的數據結構,我們來看一下有什麼不妥的地方。實際上,索引也是很“大”的,因爲索引也是存儲元素的,我們的一個表的數據行數越多,那麼對應的索引文件其實也是會很大的,實際上也是需要存儲在磁盤中的,而不能全部都放在內存中,所以我們在考慮選用哪種數據結構時,我們可以換一個角度思考,哪個數據結構更適合從磁盤中讀取數據,或者哪個數據結構能夠提高磁盤的IO效率。回頭看一下完全平衡二叉樹,當我們需要查詢“張飛”時,需要以下步驟

  1. 從磁盤中取出“曹操”到內存,CPU從內存取出數據進行比較,“張飛”<“曹操”,取左子樹(產生了一次磁盤IO)

  2. 從磁盤中取出“周瑜”到內存,CPU從內存取出數據進行比較,“張飛”>“周瑜”,取右子樹(產生了一次磁盤IO)

  3. 從磁盤中取出“孫權”到內存,CPU從內存取出數據進行比較,“張飛”>“孫權”,取右子樹(產生了一次磁盤IO)

  4. 從磁盤中取出“黃忠”到內存,CPU從內存取出數據進行比較,“張飛”=“張飛”,找到結果(產生了一次磁盤IO)

同理,回頭看一下B樹,我們發現只發送三次磁盤IO就可以找到“張飛”了,這就是B樹的優點:一個節點可以存儲多個元素,相對於完全平衡二叉樹所以整棵樹的高度就降低了,磁盤IO效率提高了

而B+樹是B樹的升級版,只是把非葉子節點冗餘一下,這麼做的好處是爲了提高範圍查找的效率

到這裏可以總結出來,Mysql選用B+樹這種數據結構作爲索引,可以提高查詢索引時的磁盤IO效率,並且可以提高範圍查詢的效率,並且B+樹裏的元素也是有序的。

那麼,一個B+樹的節點中到底存多少個元素合適呢?

其實也可以換個角度來思考B+樹中一個節點到底多大合適?

答案是:B+樹中一個節點爲一頁或頁的倍數最爲合適。因爲如果一個節點的大小小於1頁,那麼讀取這個節點的時候其實也會讀出1頁,造成資源的浪費;如果一個節點的大小大於1頁,比如1.2頁,那麼讀取這個節點的時候會讀出2頁,也會造成資源的浪費;所以爲了不造成浪費,所以最後把一個節點的大小控制在1頁、2頁、3頁、4頁等倍數頁大小最爲合適。

那麼,Mysql中B+樹的一個節點大小爲多大呢?

這個問題的答案是“1頁”,這裏說的“頁”是Mysql自定義的單位(其實和操作系統類似),Mysql的Innodb引擎中一頁的默認大小是16k(如果操作系統中一頁大小是4k,那麼Mysql中1頁=操作系統中4頁),可以使用命令SHOW GLOBAL STATUS like 'Innodb_page_size'; 查看。

並且還可以告訴你的是,一個節點爲1頁就夠了。

爲什麼一個節點爲1頁(16k)就夠了?

解決這個問題,我們先來看一下Mysql中利用B+樹的具體實現。

Mysql中MyISAM和innodb使用B+樹

通常我們認爲B+樹的非葉子節點不存儲數據,只有葉子節點才存儲數據;而B樹的非葉子和葉子節點都會存儲數據,會導致非葉子節點存儲的索引值會更少,樹的高度相對會比B+樹高,平均的I/O效率會比較低,所以使用B+樹作爲索引的數據結構,再加上B+樹的葉子節點之間會有指針相連,也方便進行範圍查找。上圖的data區域兩個存儲引擎會有不同。

MyISAM中的B+樹

MYISAM中葉子節點的數據區域存儲的是數據記錄的地址

主鍵索引

輔助索引

MyISAM存儲引擎在使用索引查詢數據時,會先根據索引查找到數據地址,再根據地址查詢到具體的數據。並且主鍵索引和輔助索引沒有太多區別。

InnoDB中的B+樹

InnoDB中主鍵索引的葉子節點的數據區域存儲的是數據記錄,輔助索引存儲的是主鍵值

主鍵索引

 

輔助索引

Innodb中的主鍵索引和實際數據時綁定在一起的,也就是說Innodb的一個表一定要有主鍵索引,如果一個表沒有手動建立主鍵索引,Innodb會查看有沒有唯一索引,如果有則選用唯一索引作爲主鍵索引,如果連唯一索引也沒有,則會默認建立一個隱藏的主鍵索引(用戶不可見)。另外,Innodb的主鍵索引要比MyISAM的主鍵索引查詢效率要高(少一次磁盤IO),並且比輔助索引也要高很多。所以,我們在使用Innodb作爲存儲引擎時,我們最好:

  1. 手動建立主鍵索引

  2. 儘量利用主鍵索引查詢

回到我們的問題:爲什麼一個節點爲1頁(16k)就夠了?

對着上面Mysql中Innodb中對B+樹的實際應用(主要看主鍵索引),可以發現B+樹中的一個節點存儲的內容是:

  • 非葉子節點:主鍵+指針

  • 葉子節點:數據

那麼,假設我們一行數據大小爲1K,那麼一頁就能存16條數據,也就是一個葉子節點能存16條數據;再看非葉子節點,假設主鍵ID爲bigint類型,那麼長度爲8B,指針大小在Innodb源碼中爲6B,一共就是14B,那麼一頁裏就可以存儲16K/14=1170個(主鍵+指針),那麼一顆高度爲2的B+樹能存儲的數據爲:117016=18720條,一顆高度爲3的B+樹能存儲的數據爲:11701170*16=21902400(千萬級條)。所以在InnoDB中B+樹高度一般爲1-3層,它就能滿足千萬級的數據存儲。在查找數據時一次頁的查找代表一次IO,所以通過主鍵索引查詢通常只需要1-3次IO操作即可查找到數據。所以也就回答了我們的問題,1頁=16k這麼設置是比較合適的,是適用大多數的企業的,當然這個值是可以修改的,所以也能根據業務的時間情況進行調整。

最左前綴原則

我們模擬數據建立一個聯合索引

select*,concat(right(emp_no,1),"-",right(title,1),"-",right(from_date,2))from employees.titles limit10;

那麼對應的B+樹爲

我們判斷一個查詢條件能不能用到索引,我們要分析這個查詢條件能不能利用某個索引縮小查詢範圍

對於 select * from employees.titles where emp_no=1是能用到索引的,因爲它能利用上面的索引所有查詢範圍,首先和第一個節點“4-r-01”比較,1<4,所以可以直接確定結果在左子樹,同理,依次按順序進行比較,逐步可以縮小查詢範圍。對於 select * from employees.titles where title='1'是不能用到索引的,因爲它不能用到上面的索引,和第一節點進行比較時,沒有empno這個字段的值,不能確定到底該去左子樹還是右子樹繼續進行查詢。對於 select *from employees.titles where title='1' and emp_no=1是能用到索引,按照我們的上面的分析,先用title='1'這個條件和第一個節點進行比較,是沒有結果的,但是mysql會對這個sql進行優化,優化之後會將empno=1這個條件放到第一位,從而可以利用索引。

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