程序員需要了解的知識點(diǎn)—MySQL索引機(jī)制
一、索引是什么
MySQL官方對索引的定義為:索引(Index)是幫助MySQL 高效 獲取數(shù)據(jù)的數(shù)據(jù)結(jié)構(gòu),而MYSQL使用的數(shù)據(jù)結(jié)構(gòu)是:B+樹
在這里推薦大家看一本書,《深入理解計(jì)算機(jī)系統(tǒng)的書》
1.1 局部性原理
程序和數(shù)據(jù)的訪問都有聚集成群的傾向,在一個(gè)時(shí)間段內(nèi),僅使用其中一小部分,在最近的將來將用到的信息很可能與現(xiàn)在正在使用的信息在空間地址上是臨近的(稱空間局部性),或者最近訪問過的程序代碼和數(shù)據(jù),很快又被訪問的可能性很大(稱時(shí)間局部性)。
1.2 磁盤預(yù)讀
預(yù)讀的長度一般為頁(page)的整數(shù)倍頁是存儲器的邏輯塊,操作系統(tǒng)往往將主存和磁盤存儲區(qū)分割成連續(xù)的大小相等的塊,每個(gè)存儲塊稱為一頁(在許多操作系統(tǒng)中,頁大小通常為4K),主存和磁盤以頁為單位交換數(shù)據(jù)
1.3 簡介
在使用數(shù)據(jù)庫中,通常數(shù)據(jù)庫查詢是數(shù)據(jù)庫的最主要功能之一。但每種查找算法都只能應(yīng)用于特定的數(shù)據(jù)結(jié)構(gòu)之上。
- 例如二分查找要求被檢索數(shù)據(jù)有序
- 而二叉樹查找只能應(yīng)用于二叉查找樹上,但是數(shù)據(jù)本身的組織結(jié)構(gòu)不可能完全滿足各種數(shù)據(jù)結(jié)構(gòu)(例如,理論上不可能同時(shí)將兩列都按順序進(jìn)行組織),所以,在數(shù)據(jù)之外,數(shù)據(jù)庫系統(tǒng)還維護(hù)著滿足特定查找算法的數(shù)據(jù)結(jié)構(gòu),這些數(shù)據(jù)結(jié)構(gòu)以某種方式引用(指向)數(shù)據(jù),這樣就可以在這些數(shù)據(jù)結(jié)構(gòu)上實(shí)現(xiàn)高級查找算法。這種數(shù)據(jù)結(jié)構(gòu),就是索引。索引一般以文件形式存儲在磁盤上,索引檢索需要磁盤I/O操作。所以評價(jià)一個(gè)數(shù)據(jù)結(jié)構(gòu)作為索引的優(yōu)劣最重要的指標(biāo)就是在查找過程中磁盤I/O操作次數(shù)的漸進(jìn)復(fù)雜度。
- 索引是幫助 MYSQL 高效獲取數(shù)據(jù)的數(shù)據(jù)結(jié)構(gòu)
- 索引存儲在文件系統(tǒng)中
- 索引的 文件存儲形式與存儲引擎有關(guān)
- 索引文件的結(jié)構(gòu):hash、二叉樹、B樹、B+樹
二、索引的分類
2.1 hash
這里有一個(gè)mysql數(shù)據(jù)文件,有Id和name兩個(gè)列,如果我們用hash格式存儲的話(hash表),我們只要計(jì)算出某一個(gè)列的hash值,把它按照按照數(shù)組的長度取一個(gè)模,就可以取到從0-7n個(gè)下標(biāo)的位置,這樣的話效率其實(shí)是比較高的,但是用hash表存儲,它具備一定的缺點(diǎn) :
- 利用hash存儲的話需要將所有的數(shù)據(jù)文件添加到內(nèi)存中,比較耗費(fèi)內(nèi)存空間
- 如果所有的查詢都是等值查詢,那么hash確實(shí)很快,但是在企業(yè)或者實(shí)際工作環(huán)境中范圍查找的數(shù)據(jù)更多,而不是等值查詢,因?yàn)閔ash就不太適合了,因此在mysql里面并沒有選擇hash存儲的格式2.2 二叉樹
索引格式:

對于樹有他是有一個(gè)更新跌過的順序在里面,不要一上來就看結(jié)構(gòu),先是了解什么樹,樹都是由一個(gè)樹根,然后有n多個(gè)分支組成,這些分支就是一些樹形結(jié)構(gòu),多你有多個(gè)樹分支(多元素)的時(shí)候,這個(gè)時(shí)候查找效率就會比較低,因此就有了二叉樹的東西,二叉樹為什么會好用一點(diǎn),因?yàn)槎鏄渌嵌加袃蓚€(gè)分支,但是兩個(gè)分支的話,會導(dǎo)致一個(gè)效果,就是每次我們在查找數(shù)據(jù)的時(shí)候,類似于二分查找的,但是二叉樹也有自己不太好的地方,大家可以看我們上圖中的二叉樹的索引格式,在左邊的節(jié)點(diǎn)會比較短一點(diǎn)(只需要讀三次),而右邊的節(jié)點(diǎn)會長很多(需要讀五次),會導(dǎo)致樹的深度比較深,每一次樹的節(jié)點(diǎn)讀取,都會有一次IO,深度越高,IO越高,會影響我們數(shù)據(jù)讀取的效率,因此也有了(平衡二叉樹)和(紅黑樹)
平衡二叉樹: 維護(hù)一個(gè)平衡,就是左子樹和右子樹高度之差,不能大于1,但是對于我們上面的格式就不太適合,因?yàn)樗呀?jīng)超過1了,但是AVL樹也會有一個(gè)問題就是調(diào)整的次數(shù)太頻繁了,它里面涉及到了一個(gè)操作就是旋轉(zhuǎn),一種左旋,一個(gè)右旋,為了保持平衡需要N多次的旋轉(zhuǎn),這樣的旋轉(zhuǎn)其實(shí)是很浪費(fèi)時(shí)間的,每次新增或者刪除的時(shí)候,都要經(jīng)歷N多次旋轉(zhuǎn),效率太低了
推薦大家一個(gè)網(wǎng)站,可以直接看到AVL樹操作過程,有不了解的同學(xué)可以去看一看,很形象:AVL Trees (Balanced binary search trees)
紅黑樹: 本身也是一個(gè)平衡樹,但是它從中間做了一個(gè)權(quán)衡,就是損失一部分平衡的性能,但是又保持了相對的平衡,它做了這樣一個(gè)操作,就是最長子樹的高度,只要不超過最短子樹的兩倍,就可以了,同時(shí)在紅黑樹中它引入了紅和黑兩個(gè)節(jié)點(diǎn)信息,有了這些信息它可以幫助我們做一個(gè)平衡,在AVL樹有旋轉(zhuǎn)保持平衡,而紅黑樹有了旋轉(zhuǎn)和變色兩種來保持平衡,紅黑樹是AVL樹的進(jìn)階,它損失了一部分平衡的性能,但是維護(hù)了我們插入和刪除數(shù)據(jù)的高效,雖然它損失了一部分性能,但是它依然是一個(gè)平衡樹,既然是平衡樹,他最長子樹,不超過最短子樹的兩倍,那意味著如果最短子樹是 4 ,那么最長子樹就是8,這樣在們查找數(shù)據(jù)的時(shí)候,又不是一個(gè)二分查找了,效率又會變低
無論是二叉樹還是紅黑樹,都會因?yàn)闃涞纳疃冗^深而造成IO次數(shù)變多,影響數(shù)據(jù)的讀取的效率,最重要的就是減少IO
IO是我們IT行業(yè)中的一個(gè)瓶頸,一個(gè)是磁盤IO一個(gè)是網(wǎng)絡(luò)IO,我們作為軟件開發(fā),是沒有辦法去調(diào)整硬件方面的瓶頸,只能從從程序里面減少我們的IO量,我們有兩個(gè)方向,一個(gè)是減少IO的次數(shù),一個(gè)是減少IO的量,從這兩個(gè)方面去解決,比如說原來我們讀取數(shù)據(jù)要讀10次,現(xiàn)在只要讀取一次,這樣的IO量就少了10倍,原來我們需要讀1MB的數(shù)據(jù),現(xiàn)在只要讀1KB的數(shù)據(jù),這也就是為什么我們在寫mysql查詢語句的時(shí)候不推薦使用select * from ,因?yàn)檫@樣的查詢會查詢到N多個(gè)字段,本來我只要兩個(gè)字段,但是給了我30個(gè)字段,這樣會導(dǎo)致IO量增加了,因此我們就會去考慮,關(guān)于索引的次數(shù)能不能減少,因此下面就引出了我們的——B樹
2.3 B樹
B樹的特點(diǎn):
- 所有的鍵值分布在整顆樹中
- 搜索有可能在非葉子結(jié)點(diǎn)結(jié)束,在關(guān)鍵字全集內(nèi)做一次查找,性能逼近二分查找
- 每個(gè)節(jié)點(diǎn)最多擁有m個(gè)子樹
- 根節(jié)點(diǎn)至少有2個(gè)子樹
- 分支節(jié)點(diǎn)至少擁有m/2顆子樹(除根節(jié)點(diǎn)和葉子節(jié)點(diǎn)外都是分支節(jié)點(diǎn))
- 所有葉子節(jié)點(diǎn)都在同一層,每個(gè)節(jié)點(diǎn)最多可以有m-1個(gè)key,并且以升序排列
B樹結(jié)構(gòu)說明:
示例圖說明:每個(gè)節(jié)點(diǎn)占用一個(gè)磁盤塊,一個(gè)節(jié)點(diǎn)上有兩個(gè)升序排序的關(guān)鍵字和三個(gè)指向子樹根節(jié)點(diǎn)的指針,指針存儲的是子節(jié)點(diǎn)所在磁盤塊的地址,兩個(gè)關(guān)鍵詞劃分成的三個(gè)范圍域?qū)?yīng)三個(gè)指針指向的子樹的數(shù)據(jù)的范圍域。以根節(jié)點(diǎn)為列,關(guān)鍵字為16和34,p1指針指向的子樹的數(shù)據(jù)范圍小于16,P2指針指向的子樹的數(shù)據(jù)范圍為16-34,P3指針指向的子樹的數(shù)據(jù)范圍大于34 查找關(guān)鍵字(28)過程:
- 根據(jù)節(jié)點(diǎn)找到磁盤塊1,讀取內(nèi)存【磁盤I/O操作第1次】
- 比較關(guān)鍵字28在區(qū)間(16,34)找到磁盤塊1的指針P2
- 根據(jù)P2指針找到磁盤塊3,讀入內(nèi)存【磁盤I/O操作第2次】
- 比較關(guān)鍵字28在區(qū)間(25,31),找到磁盤塊3的指針P2
- 根據(jù)P2指針找到磁盤塊8,讀取內(nèi)存,【磁盤I/O操作第3次】
- 在磁盤塊8中的關(guān)鍵字列表找到關(guān)鍵字28
缺點(diǎn):
- 每個(gè)節(jié)點(diǎn)都有key,同時(shí)也包含data,而每個(gè)頁存儲空間是有限的,如果data比較大的話會導(dǎo)致每個(gè)節(jié)點(diǎn)存儲的key數(shù)量變小
- 當(dāng)存儲的數(shù)據(jù)量很大的時(shí)候會導(dǎo)致深度較大,增大查詢時(shí)磁盤IO次數(shù),進(jìn)而影響查詢性能
2.4 B+樹
B+Tree 是在BTree 的基礎(chǔ)之上做的一種優(yōu)化,變化如下:
- B+Tree 每個(gè)節(jié)點(diǎn)可以包含更多的節(jié)點(diǎn),這個(gè)做的 原因有兩個(gè),第一個(gè)原因是為了降低樹的高度,第二個(gè)原因是將數(shù)據(jù)范圍變?yōu)槎鄠€(gè)區(qū)間,區(qū)間越多,數(shù)據(jù)檢索的越快
- 非葉子節(jié)點(diǎn)存儲key(1,2,3磁盤都是存儲的key),葉子節(jié)點(diǎn)存儲key和數(shù)據(jù)
- 葉子節(jié)點(diǎn)兩兩指針相互連接(符合磁盤的預(yù)讀特性)順序查詢性能更高如果當(dāng)前磁盤塊下沒有其他節(jié)點(diǎn),就是 葉子節(jié)點(diǎn),反之就是 非葉子節(jié)點(diǎn)
結(jié)構(gòu)圖:
注意:在B+Tree上有兩個(gè)頭指針,一個(gè)指向根節(jié)點(diǎn),另一個(gè)指向關(guān)鍵字最小的葉子節(jié)點(diǎn),而且所有的葉子節(jié)點(diǎn)(即數(shù)據(jù)節(jié)點(diǎn))之間是一種鏈?zhǔn)江h(huán)結(jié)構(gòu),因此可以對B+Tree進(jìn)行兩種查詢運(yùn)算,一種是對于主鍵的范圍查找和分頁查找,另一種是從根節(jié)點(diǎn)開始,進(jìn)行隨機(jī)查找。
三、mysql的存儲引擎
3.1 mysql innoDB (葉子節(jié)點(diǎn)直接放置數(shù)據(jù))
3.1 mysql innoDB (葉子節(jié)點(diǎn)直接放置數(shù)據(jù))
存放的是對應(yīng)的行記錄
1、InnoDB是通過B+Tree結(jié)構(gòu)對主鍵創(chuàng)建索引,然后葉子節(jié)點(diǎn)中存儲記錄,如果沒有主鍵,那么會選擇唯一鍵,如果沒有唯一鍵,那么會生成一個(gè)6位的row_id來作為主鍵
2、如果創(chuàng)建索引的鍵是其他字段,那么在葉子節(jié)點(diǎn)中存儲的是該記錄的主鍵,然后在通過主鍵索引找到對應(yīng)的記錄
在name上建立索引
在name列上存放的是ID,然后通過ID去找到對應(yīng)的key和數(shù)據(jù)
3.1 mysql MyISAM
下面0X0022其實(shí)就是地址,顯示根據(jù)我們的ID,找到我們的地址,然后通過地址去找到對應(yīng)的表對應(yīng)的數(shù)據(jù)
四、索引的分類
mysql索引的五種類型:主鍵索引、唯一索引、普通索引和全文索引、組合索引。通過給字段添加索引可以提高數(shù)據(jù)的讀取速度,提高項(xiàng)目的并發(fā)能力和抗壓能力
- 主鍵索引:> 主鍵是一種唯一性索引,但它必須指定為PRIMARY KEY,每個(gè)表只能有一個(gè)主鍵
- 唯一索引 > 索引列的所有值都只能出現(xiàn)一次,即必須唯一,值可以為空
- 普通索引 > 基本的索引類型,值可以為空,沒有唯一性的限制
- 全文索引 > 全文索引的索引類型為FULLTEXT,全文索引可以在 varchar、char、text類型的列上創(chuàng)建
- 組合索引 > 多列值組成的一個(gè)索引,專門用于組合搜索
五、mysql的存儲引擎
小結(jié)
寫這篇文章的時(shí)候,小農(nóng)的公司群消息不斷,因?yàn)轫?xiàng)目中有問題需要我去解決,今天的mysql索引機(jī)制就到這里了.