Mysql索引機制(B+Tree)

 1,索引誰實現的:

  索引是搜索引擎去實現的,在創建表的時候都會指定,搜索引擎是一種插拔式的,根據本身的選擇去決定使用哪個。mysql

2,索引的定義:

  索引是爲了加速對錶中數據行的檢索而建立的一種分散存儲的(不連續的)數據結構,硬盤級的。sql

  索引意義:索引能極大的減小存儲引擎須要掃描的數據量,索引能夠把隨機IO變成順序IO。索引能夠幫助咱們在進行分組、排序等操做時,避免使用臨時表。正確的建立合適的索引是提高數據庫查詢性能的基礎。數據庫

3,爲何選擇B+Tree:

  B+樹索引是B+樹在數據庫中的一種實現,是最多見也是數據庫中使用最爲頻繁的一種索引。B+樹中的B表明平衡(balance),而不是二叉(binary),由於B+樹是從最先的平衡二叉樹演化而來的。先了解二叉查找樹、平衡二叉樹(AVLTree)和平衡多路查找樹(B-Tree),B+樹即由這些樹逐步優化而來。數據結構

二叉查找樹:

  二叉樹具備如下性質:左子樹的鍵值小於根的鍵值,右子樹的鍵值大於根的鍵值。 以下圖所示就是一棵二叉查找樹,:函數

  對該二叉樹的節點進行查找發現深度爲1的節點的查找次數爲1,深度爲2的查找次數爲2,深度爲n的節點的查找次數爲n,所以其平均查找次數爲 (1+2+2+3+3+3) / 6 = 2.3次。二叉查找樹能夠任意地構造,一樣是2,3,5,6,7,8這六個數字,也能夠按照下圖的方式來構造: 性能

  可是這棵二叉樹的查詢效率就低了。所以若想二叉樹的查詢效率儘量高,須要這棵二叉樹是平衡的,從而引出新的定義——平衡二叉樹,或稱AVL樹。優化

平衡二叉樹(AVL Tree):

  平衡二叉樹(AVL樹)在符合二叉查找樹的條件下,還知足任何節點的兩個子樹的高度最大差爲1。下面的兩張圖片,左邊是AVL樹,它的任何節點的兩個子樹的高度差<=1;右邊的不是AVL樹,其根節點的左子樹高度爲3,而右子樹高度爲1; 搜索引擎

  若是在AVL樹中進行插入或刪除節點,可能致使AVL樹失去平衡,這種失去平衡的二叉樹能夠歸納爲四種姿態:LL(左左)、RR(右右)、LR(左右)、RL(右左)。它們的示意圖以下: spa

  這四種失去平衡的姿態都有各自的定義: 
LL:LeftLeft,也稱「左左」。插入或刪除一個節點後,根節點的左孩子(Left Child)的左孩子(Left Child)還有非空節點,致使根節點的左子樹高度比右子樹高度高2,AVL樹失去平衡。設計

RR:RightRight,也稱「右右」。插入或刪除一個節點後,根節點的右孩子(Right Child)的右孩子(Right Child)還有非空節點,致使根節點的右子樹高度比左子樹高度高2,AVL樹失去平衡。

LR:LeftRight,也稱「左右」。插入或刪除一個節點後,根節點的左孩子(Left Child)的右孩子(Right Child)還有非空節點,致使根節點的左子樹高度比右子樹高度高2,AVL樹失去平衡。

RL:RightLeft,也稱「右左」。插入或刪除一個節點後,根節點的右孩子(Right Child)的左孩子(Left Child)還有非空節點,致使根節點的右子樹高度比左子樹高度高2,AVL樹失去平衡。

  AVL樹失去平衡以後,能夠經過旋轉使其恢復平衡。下面分別介紹四種失去平衡的狀況下對應的旋轉方法。

LL的旋轉。LL失去平衡的狀況下,能夠經過一次旋轉讓AVL樹恢復平衡。步驟以下:

  1. 將根節點的左孩子做爲新根節點。
  2. 將新根節點的右孩子做爲原根節點的左孩子。
  3. 將原根節點做爲新根節點的右孩子。

LL旋轉示意圖以下: 

RR的旋轉:RR失去平衡的狀況下,旋轉方法與LL旋轉對稱,步驟以下:

  1. 將根節點的右孩子做爲新根節點。
  2. 將新根節點的左孩子做爲原根節點的右孩子。
  3. 將原根節點做爲新根節點的左孩子。

LR的旋轉:LR失去平衡的狀況下,須要進行兩次旋轉,步驟以下:

  1. 圍繞根節點的左孩子進行RR旋轉。
  2. 圍繞根節點進行LL旋轉。

RL的旋轉:RL失去平衡的狀況下也須要進行兩次旋轉,旋轉方法與LR旋轉對稱,步驟以下:

  1. 圍繞根節點的右孩子進行LL旋轉。
  2. 圍繞根節點進行RR旋轉。

   那麼使用平衡二叉樹做爲索引數據結構的話會是怎麼樣的呢?先看一下下圖:

  能夠把每一個節點當作一個磁盤塊,每一個磁盤塊存儲的信息如右邊這個結構圖所示。

  關鍵字:即咱們創建索引的關鍵字段的對應值。

  數據區:即關鍵字對應的數據存儲磁盤位置,經過關鍵字所對應的磁盤位置進行IO讀寫操做獲取數據。

  節點引用:即指向子節點的磁盤位置。

  若是要查找ID爲8的數據,那麼先會獲取根節點10加載到內存中,比較數據大小,發現比10小,那麼查找左節點5,發現比5大,查找5的右節點,發現命中,而後根據數據區地址去進行IO讀寫操做。可是B-Tree有以下缺點:

  它太深了,數據處的(高)深度決定着他的IO操做次數,IO操做耗時大。它過小了,每個磁盤塊(節點/頁)保存的數據量過小了。沒有很好的利用操做磁盤IO的數據交換特性,一次IO操做以頁爲單位 ,4KB,那麼加載一次絕對不會達到4KB.也沒有利用好磁盤IO的預讀能力(空間局部性原理),從而帶來頻繁的IO操做

平衡多路查找樹(B-Tree):

  B-Tree是爲磁盤等外存儲設備設計的一種平衡查找樹。所以在講B-Tree以前先了解下磁盤的相關知識。

  系統從磁盤讀取數據到內存時是以磁盤塊(block)爲基本單位的,位於同一個磁盤塊中的數據會被一次性讀取出來,而不是須要什麼取什麼。InnoDB存儲引擎中有頁(Page)的概念,頁是其磁盤管理的最小單位。InnoDB存儲引

擎中默認每一個頁的大小爲16KB,可經過參數innodb_page_size將頁的大小設置爲4K、8K、16K,在MySQL中可經過以下命令查看頁的大小:mysql> show variables like 'innodb_page_size';

  而系統一個磁盤塊的存儲空間每每沒有這麼大,所以InnoDB每次申請磁盤空間時都會是若干地址連續磁盤塊來達到頁的大小16KB。InnoDB在把磁盤數據讀入到磁盤時會以頁爲基本單位,在查詢數據時若是一個頁中的每條數據都

能有助於定位數據記錄的位置,這將會減小磁盤I/O次數,提升查詢效率。B-Tree結構的數據可讓系統高效的找到數據所在的磁盤塊。爲了描述B-Tree,首先定義一條記錄爲一個二元組[key, data] ,key爲記錄的鍵值,對應表中的主鍵

值,data爲一行記錄中除主鍵外的數據。對於不一樣的記錄,key值互不相同。一棵m階的B-Tree有以下特性: 

1. 每一個節點最多有m個孩子。 

2. 除了根節點和葉子節點外,其它每一個節點至少有Ceil(m/2)個孩子。 

3. 若根節點不是葉子節點,則至少有2個孩子 

4. 全部葉子節點都在同一層,且不包含其它關鍵字信息 

5. 每一個非終端節點包含n個關鍵字信息(P0,P1,…Pn, k1,…kn) 

6. 關鍵字的個數n知足:ceil(m/2)-1 <= n <= m-1 

7. ki(i=1,…n)爲關鍵字,且關鍵字升序排序。 

8. Pi(i=1,…n)爲指向子樹根節點的指針。P(i-1)指向的子樹的全部節點關鍵字均小於ki,但都大於k(i-1)

  B-Tree中的每一個節點根據實際狀況能夠包含大量的關鍵字信息和分支,以下圖所示爲一個3階的B-Tree:

  每一個節點佔用一個盤塊的磁盤空間,一個節點上有兩個升序排序的關鍵字和三個指向子樹根節點的指針,指針存儲的是子節點所在磁盤塊的地址。兩個關鍵詞劃分紅的三個範圍域對應三個指針指向的子樹的數據的範圍域。以根節點爲例,關鍵字爲17和35,P1指針指向的子樹的數據範圍爲小於17,P2指針指向的子樹的數據範圍爲17~35,P3指針指向的子樹的數據範圍爲大於35。模擬查找關鍵字29的過程:

  1. 根據根節點找到磁盤塊1,讀入內存。【磁盤I/O操做第1次】
  2. 比較關鍵字29在區間(17,35),找到磁盤塊1的指針P2。
  3. 根據P2指針找到磁盤塊3,讀入內存。【磁盤I/O操做第2次】
  4. 比較關鍵字29在區間(26,30),找到磁盤塊3的指針P2。
  5. 根據P2指針找到磁盤塊8,讀入內存。【磁盤I/O操做第3次】
  6. 在磁盤塊8中的關鍵字列表中找到關鍵字29。

  分析上面過程,發現須要3次磁盤I/O操做,和3次內存查找操做。因爲內存中的關鍵字是一個有序表結構,能夠利用二分法查找提升效率。而3次磁盤I/O操做是影響整個B-Tree查找效率的決定因素。B-Tree相對於AVLTree縮減了節點個數,使每次磁盤I/O取到內存的數據都發揮了做用,從而提升了查詢效率。

  咱們能夠來算一筆帳,以InnoDB存儲引擎中默認每一個頁的大小爲16KB來計算,假設以int型的ID做爲索引關鍵字,那麼 一個int佔用4byte,由上圖能夠知道還有其餘的除主鍵之外的數據,姑且頁當成4byte,那麼這裏就是8byte,那麼16KB=16*1024byte,那麼咱們在這種場景下,能夠定義這個B-Tree的階樹爲 (16*1024)/8=2048.那麼這個樹將會有2048-1路,也就是原來平衡二叉樹(兩路)的1024倍左右,從而大大提升了查找效率與下降IO讀寫次數。

  B-Tree爲了保證絕對平衡他有本身的機制,好比每一個節點上的關鍵字個數=路數(階數-1),以下圖:

  能夠看到添加節點後違反了原有的規則,這個時候會進行分裂。結果就會造成一根最新的樹,(若是分裂過程當中23 333 這個節點頁不知足了會繼續向上分裂):

  因此創建合適的索引是很重要的 ,不宜多,當加一條數據,整棵樹會進行重組。

B+Tree:

  B+Tree是在B-Tree基礎上的一種優化,使其更適合實現外存儲索引結構,InnoDB存儲引擎就是用B+Tree實現其索引結構。

  從 B-Tree 結構圖中能夠看到每一個節點中不只包含數據的key值,還有data值。而每個頁的存儲空間是有限的,若是data數據較大時將會致使每一個節點(即一個頁)能存儲的key的數量很小,當存儲的數據量很大時一樣會致使B-Tree的深度較大,增大查詢時的磁盤I/O次數,進而影響查詢效率。在B+Tree中,全部數據記錄節點都是按照鍵值大小順序存放在同一層的葉子節點上,而非葉子節點上只存儲key值信息,這樣能夠大大加大每一個節點存儲的key值數量,下降B+Tree的高度。

B+Tree相對於B-Tree有幾點不一樣:

  1. B+節點關鍵字搜索採用閉合區間
  2. B+非葉節點不保存數據相關信息,只保存關鍵字和子節點的引用
  3. B+關鍵字對應的數據保存在葉子節點中
  4. B+葉子節點是順序排列的,而且相鄰節點具備順序引用的關係

  將B-Tree優化,因爲B+Tree的非葉子節點只存儲鍵值信息,假設每一個磁盤塊能存儲4個鍵值及指針信息,則變成B+Tree後其結構以下圖所示: 

  一般在B+Tree上有兩個頭指針,一個指向根節點,另外一個指向關鍵字最小的葉子節點,並且全部葉子節點(即數據節點)之間是一種鏈式環結構。所以能夠對B+Tree進行兩種查找運算:一種是對於主鍵的範圍查找和分頁查找,另外一種是從根節點開始,進行隨機查找。可能上面例子中只有22條數據記錄,看不出B+Tree的優勢,下面作一個推算:InnoDB存儲引擎中頁的大小爲16KB,通常表的主鍵類型爲INT(佔用4個字節)或BIGINT(佔用8個字節),指針類型也通常爲4或8個字節,也就是說一個頁(B+Tree中的一個節點)中大概存儲16KB/(8B+8B)=1K個鍵值(由於是估值,爲方便計算,這裏的K取值爲〖10〗^3)。也就是說一個深度爲3的B+Tree索引能夠維護10^3 * 10^3 * 10^3 = 10億 條記錄。

  實際狀況中每一個節點可能不能填充滿,所以在數據庫中,B+Tree的高度通常都在2~4層。mysql 的InnoDB存儲引擎在設計時是將根節點常駐內存的,也就是說查找某一鍵值的行記錄時最多隻須要1~3次磁盤I/O操做。數據庫中的B+Tree索引能夠分爲彙集索引(clustered index)和輔助索引(secondary index)。上面的B+Tree示例圖在數據庫中的實現即爲彙集索引,彙集索引的B+Tree中的葉子節點存放的是整張表的行記錄數據。輔助索引與彙集索引的區別在於輔助索引的葉子節點並不包含行記錄的所有數據,而是存儲相應行數據的彙集索引鍵,即主鍵。當經過輔助索引來查詢數據時,InnoDB存儲引擎會遍歷輔助索引找到主鍵,而後再經過主鍵在彙集索引中找到完整的行記錄數據。

  B+Tree在MYSQL中採用的是左閉合區間,MYSQL推崇使用ID做爲索引,因爲ID是自增的數字類型,只會增大,因此採用向右拓展的一個方式,從根節點進行比對,因爲枝節點不保存數據,無所謂命不命中,都要繼續走到葉子節點才能加載數據。

B+樹是B-樹的變種(PLUS版)多路絕對平衡查找樹,他擁有B-樹的優點。
B+樹掃庫、表能力更強。
B+樹的磁盤讀寫能力更強。
B+樹的排序能力更強。
B+樹的查詢效率更加穩定(仁者見仁、智者見智)。

4,B+Tree在兩大引擎中如何體現:

  索引的實現是由搜索引擎來實現的,那麼在 MYSQL中比較主流的兩大引擎是:Myisam 跟 innoDB,存儲引擎是創建在表上面的,在創建表的時候能夠指定所須要的搜索引擎。例以下列的建立語句中就指定了搜索引擎爲:ENGINE=InnoDB,不指定就使用默認的InnoDB

CREATE TABLE `user` (
  `id` int(11) NOT NULL,
  `name` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

  B+Tree 在 Myisam 中的體現:

  在建立好表結構而且指定搜索引擎爲 Myisam以後,會在數據目錄生成3個文件,分別是table_name.frm(表結構文件),table_name.MYD(數據保存文件),table_name.MYI(索引保存文件)。

  例如上訴 teacher表,兩個文件分別保存了數據及索引,因爲B+Tree中只有葉子節點保存數據區,在Myisam中,數據區中保存的是數據的引用地址,就好比說ID爲101的數據信息所保存到物理磁盤地址爲 0x123456,在索引中的節點數據去中所保存的就是這個磁盤地址指針。當掃描到這個指針位置,就能夠經過這個磁盤指針講數據加載出來。

  在Myisam中B+Tree的實現中好比如今不用ID做爲索引了,要用name,那麼他的一個展示形式有事怎麼樣的呢?其實他與ID做爲索引是同樣的,也是保存他指定的磁盤位置指針,他們是平級的。以下圖:

  B+Tree 在 InnoDB 中的體現:

  在建立好表結構而且指定搜索引擎爲 Myisam以後,會在數據目錄生成3個文件,分別是table_name.frm(表結構文件),table_name.idb(數據與索引保存文件)。

  在 InnoDB中,由於設計之初就是認爲主鍵是很是重要的。是以主鍵爲索引來組織數據的存儲,當咱們沒有顯示的創建主鍵索引的時候,搜索引擎會隱式的爲咱們創建一個主鍵索引以組織數據存儲。數據庫錶行中數據的物理順序與鍵值的邏輯(索引)順序相同,InnoDB就是以彙集索引來組織數據的存儲的,在葉子節點上,保存了數據的全部信息。若是這個時候創建了name字段的索引:

  會產生一個輔助索引,即name字段的索引,而此刻葉子節點上所保存的數據爲 彙集索引(ID索引)的關鍵字的值,基於輔助索引找到ID索引的值,再經過ID索引區獲取最終的數據。這個作法的好處是在於產生數據遷移的時候只要ID沒發生變法,那麼輔助索引不須要從新生成,不這麼作的話,若是存儲的是磁盤地址的話,在數據遷移後全部輔助索引都須要從新生成。

5,索引知識:

找出離散性最好的列:

  越大離散型越好 count(distinct col):count(col) 理解爲差別性。結論:離散性越高選擇性就越好,好比有一個性別的字段的索引,假設男爲1,女爲0:就會生成一下一個索引樹:

  這個時候要搜索女的數據,那麼在根節點觸發,能夠由兩條路能夠走,從中間走下去的話發現能夠選擇的線路太多了,這樣會致使搜索引擎懵逼,優化器以爲既然要搜索這麼多數據,還不如全表掃描呢,這就致使離散型下降。不利於性能。

最左匹配原則:

  對索引中關鍵字進行計算(對比),必定是從左往右依次進行,且不可跳過,在建立數據庫的時候須要選擇字符集及排序規則,這都是有用的 ,好比一棵B-tree中的根節點爲一個字符串 abc ,那麼我如今要搜索一個爲 adc的索引關鍵字的數據,根節點abc的ASCII 碼爲 97 98 99,而 adc的爲 97 100 99,那麼和3個數字會逐一比對,且100>98,接下去必定會走右子樹。

聯合索引:

  單列索引:節點中關鍵字[name] 及索引的關鍵字的值爲那麼對應的值 ,好比 張三。

  聯合索引:節點中關鍵字[name,phoneNum]好比張三,138888888。

  聯合索引列選擇原則。

  1. 常常用的列優先 【最左匹配原則】。
  2. 選擇性(離散度)高的列優先【離散度高原則】。
  3. 寬度小的列優先【最少空間原則】。

   示例:經排查發現最經常使用的sql語句:Select * from users where name = ? ;Select * from users where name = ? and phoneNum = ?;

  機靈的李二狗的解決方案:create index idx_name on users(name);--(冗餘索引  最左原則,下面這個聯合索引適用於以上2個sql語句);create index idx_name_phoneNum on users(name,phoneNum);

   因此在這種狀況下只須要創建一個聯合索引便可,會根據最左匹配原則去匹配的。

覆蓋索引:

  若是查詢列可經過索引節點中的關鍵字直接返回,則該索引稱之爲覆蓋索引。覆蓋索引可減小數據庫IO,將隨機IO變爲順序IO,可提升查詢性能。比說所創建了一個聯合索引 reate index idx_name_phoneNum on users(name,phoneNum);而此刻有sql select name phoneNum from 。。。。這個就是覆蓋索引。

6,總結及驗證:

  索引列的數據長度能少則少。索引必定不是越多越好,越全越好,必定是建合適的。查詢條件上有計算函數沒法命中索引。

  匹配列前綴:like 9999%(最原則上按照左匹配上來講是能夠的,可是不必定能用到索引,當離散性太差的時候就不行),like %9999%(不行)、like %9999(不行)用不到索引;

  Where 條件中 not in 和 <>操做沒法使用索引;匹配範圍值,order by 也可用到索引;多用指定列查詢,只返回本身想到的數據列,少用select *;

  聯合索引中若是不是按照索引最左列開始查找,沒法使用索引;

  聯合索引中精確匹配最左前列並範圍匹配另一列能夠用到索引;好比聯合索引【name,phoneNum】,當SQL爲:select .....where name='1' and phoneNum>xxxxxxx.

  聯合索引中若是查詢中有某個列的範圍(大於小於)查詢,則其右邊的全部列都沒法使用索引;

最後送上一首網友提供的打油詩:

  全值匹配我最愛,最左前綴要遵照;  帶頭大哥不能死,中間兄弟不能斷;  索引列上少計算,範圍以後全失效;  Like百分寫最右,覆蓋索引不寫星;  不等空值還有or,索引失效要少用;  VAR引號不可丟,SQL高級也不難!

相關文章
相關標籤/搜索