索引是面試必不可少的內容,接下來帶你進入MySQL索引的世界。html
你是否是對於MySQL索引的知識點一直都像大雜燴,好像什麼都知道,若是進行深究的話可能一個也答不上來。面試
假如你去面試,面試官讓你聊一下對索引的理解,然而你對索引的理解僅限於,檢索數據就是快,是一種數據結構這個層面,那你就只能回家等通知了。sql
爲了不這種尷尬的事情發生,咔咔用時兩天將索引的內容在本身理解的範圍內進行整理,如整理的不全面能夠在評論區進行補充和提建議。數據庫
相信大多數夥伴都買過技術類的書籍,看完沒看完不知道,可是目錄確定看的次數最多。服務器
看目錄有沒有本身目前的痛點,若是有就會根據目錄對應的頁碼用最快的速度翻閱到相應內容位置。markdown
那麼在MySQL中一樣也是這樣的一個道理,MySQL的索引就是存儲引擎爲了快速找到數據的一種數據結構
數據結構
一樣在MySQL索引中又分了幾種類型,分別爲B-tree索引、哈希索引、空間索引、全文索引。函數
下文全部內容均在Innodb的基礎上討論。性能
索引能夠加快數據檢索速度
,這也是使用的索引的最主要緣由。優化
索引自己具備順序性,在進行範圍查詢時,獲取的數據已經排好了序,從而避免服務器再次排序和創建臨時表的問題
。
索引的底層實現自己具備順序性,經過磁盤預讀使得在磁盤上對數據的訪問大體呈順序的尋址,也就是將隨機的I/O變爲順序I/O
。
這幾點不理解就暫時先放着,繼續看下文便可,會給你一個滿意的解釋。
任何事物都存在雙面性,既然能提供性能的提高,天然在其它方面也會付出額外的代價。
索引是跟數據共存,所以會佔用額外的存儲空間。
索引建立和維護須要時間成本,這個成本隨着數據量的增大而增大。
索引建立會下降數據的增、刪、改的性能,由於在修改數據的同時還須要修改索引數據。
聊到這個問題那就必須得分清楚BTree、B+tree的區別,首先來看一下BTree
先來看一下BTree的數據結構是怎麼樣的,這裏咔咔給提供一個網站地址https://www.cs.usfca.edu/~galles/visualization/Algorithms.html
,能夠看到關於數據結構的一些實現過程。
先來看BTree的數據結構,下圖是咔咔已經將數據填充進去的。
這裏有一個陌生區關於Max. Degree
,這個你能夠理解爲階,也能夠理解爲度。
例如如今這個值設置的是4,那麼在一個節點中最多就能夠存儲三條數據,設置爲5那就能夠最多放4條記錄。
如今能夠看到目前只插入了三條數據。
那麼再加一條數據,節點就會進行分裂,這個也就驗證了當階設置爲n時,一個節點可存n-1條數據。
那接着再來插入幾條數據看看。
想要達到快速檢索數據,那就須要知足倆個特性,一個是有序,另外一個就是平衡。
從下圖中能夠看到BTree是有必定的順序性的,平衡性更知足,能夠看上文中生成的第一張圖。
那麼在BTree中找一個值是怎麼找呢!
例如如今要找一個值9
,看一下尋找過程。
首先看到的數據是4,9是大於4的,因此會往4的右節點尋找。
繼續找到範圍在6到8的節點,9又大於8,因此還須要往右節點尋找。
最有一步就找到了數據9,這個過程就是BTree數據結構查找數據的執行過程。
瞭解到了BTree的數據結構後,咱們在來看看在MySQL中關於BTree是如何存儲的。
在下圖中P表明的是指針,指向的是下一個磁盤塊。
在第一個節點中的1六、24就是表明咱們的key值是什麼。
date就是這個key值對應的這一行記錄是什麼。
那麼此時想要尋找key爲33的這條記錄應該怎麼找。
33在16和34中間,因此會去磁盤3進行尋找。
在磁盤3中進行判斷,指針指向磁盤8。
在磁盤8中便可獲取到數據33,而後將data返回。
那麼在這個過程當中到底讀取了多少條數據呢!
在計算以前須要先了解一些知識點。
從MySQL5.7開始,存儲引擎默認爲innodb,而且innodb存儲引擎用於管理數據的最小磁盤單位就是頁。
這個頁的類型也分爲好幾種,分別爲數據頁,Undo頁,系統頁,事物數據頁。
通常說到的頁都是數據頁。默認的頁面大小爲16kb,每一個頁中至少存儲2條或以上的行記錄。
那麼根據BTree數據查找的過程當中能夠得知一共讀取了三個磁盤,那麼每一個磁盤的大小就是16kb。
而目前的給的案例尋找了三層,那麼三層存儲的數據就是16kb * 16kb * 16kb = 4096kb。
若是按照一條記錄所需內存1kb,那麼這三層的BTree就能夠存儲4096條記錄。
各位數據庫的數據少則幾百萬,多則幾千萬數據,那麼BTree的層級就會愈來愈深,相對的查詢效率也會愈來愈慢。
這個時候是否是應該思考一個問題,那就是爲何在Btree中48kb的內存怎麼就只能存儲4000多條記錄
問題就出如今data上,要知道在計算數據大小時指針地址和key的內存都是沒有計算在內的,單單就計算了data的內存。
由於在BTree結構中,節點中不只存儲的有key、指針地址還有對應的數據,因此就會形成單個磁盤存儲的數據相對不多的緣由。
爲了解決單個節點存儲數據量小的問題,因而就演變出另外一種結構,也就是下文提到了B+Tree
依然如初看一下B+Tree的數據結構。
爲了方便對比,將BTree和B+Tree的數據結構放到了一塊兒。
那麼能夠看到在B+Tree中葉子節點是存放了全量的數據,而非葉子節點只存儲了key值。
咦!這不是就很好的解決了BTree帶來的問題嗎?可讓每一個節點存儲更多的數據。每一個節點存儲的數據越多,那麼相對的就是樹的深度就不會過深。
瞭解到了B+Tree的數據結構後,咱們在來看看在MySQL中關於B+Tree是如何存儲的。
從上圖很明顯就能夠看到倆點不一樣。
第一點:B+Tree全部的數據都存儲在葉子節點上。
第二點:B+Tree全部的葉子節點之間是一種鏈式環結構
那麼在這個過程當中到底讀取了多少條數據呢!
若是說B+Tree讀取數據的深度跟B-Tree的深度同樣,都是三層,那麼一樣的道理每一個磁盤的大小爲16kb。
那在B+Tree中非葉子節點能夠存儲多少數據呢!通常來講咱們每一個表都會存在一個主鍵。
根據三層來計算,第一層跟第二層存儲的是key值,也就是主鍵值。
都知道int類型所佔的內存時4Byte(字節),指針的存儲就給個6Byte,一共就是10Tybe,那麼第一層節點就能夠存儲16 * 1000 /10 = 1600。
同理第二層每一個節點也是能夠存儲1600個key。
第三層是葉子節點,每一個磁盤存儲大小一樣安裝BTree的計算同樣,每條數據佔1kb。
那麼在B+Tree中三層能夠存儲的數據就是1600 * 1600 * 16 = 40960000
從這點來看B+Tree存儲的數據跟BTree存儲的數據根本就不是一個級別。
因此能夠得出結論:
B+Tree能保證檢索的數據量相對BTree是最多的,並且存儲的數據量也是最多的
B+Tree選擇索引時儘可能選擇所佔內存空間小的類型,好比int類型。
key所佔內存越小,在節點中存儲的範圍就越多。
先來建立一個hash索引alter table user add index hash_gender using hash(gender);
存儲引擎使用的是innodb。
會發現name的索引類型仍是爲Btree,在innodb上建立哈希索引,被稱之爲僞哈希索引,和真正的哈希索引不是一回事的,這點必定要明白。
在Innodb存儲引擎中有一個特殊的功能叫作,自適應哈希索引,當索引值被使用的很是頻繁時,它會在內存中基於BTree索引之上再建立一個哈希索引,那麼就擁有了哈希索引的一些特色,好比快速查找
哈希索引就是基於哈希表實現的,假設對 name 創建了哈希索引,則查找過程以下圖所示,哈希表是根據鍵值對進行訪問的數據結構,它讓檢索的數據通過哈希函數映射到散列表的對應位置,查找效率很是高。
哈希索引存儲的是哈希值和行指針,沒有存儲key值、字段值,但哈希索引多數是在內存完成的,檢索數據是很是快的,因此對性能影響不大。
哈希索引不是按照索引值排序的,因此也就沒法排序。
哈希索引只支持等值操做,不支持範圍查找,在MySQL中只能只用 =、in 、<>
哈希索引在任什麼時候候都不能避免表掃描
哈希索引在遇到大量哈希衝突時,存儲引擎必須遍歷鏈表的全部行指針,逐行比較。
通過了特別漫長的計算、畫圖如今基本對倆者的區別有必定認識了吧!
咔咔在這裏進行總結一下。
B+Tree樹非葉子節點只存儲key值,所以相對於BTree節點能夠存儲更多的數據,每次讀入內存的key值就更多,相對來講I/O就下降
B+Tree樹查詢效率穩定,任何數據的查找都是必須從葉子節點到非葉子節點,因此說每一個數據查找的效率幾乎都是相同的。
B+Tree樹的葉子節點存儲的是全量數據,而且是有序的,因此說只須要遍歷葉子節點就能夠對全部的key進行掃描,在範圍查找時效率更高。
以上就是關於Innodb存儲引擎爲何使用B+Tree做爲索引的解析。
聚簇索引、非聚簇索引也被稱之爲主索引、二級索引。
那麼如何區分聚簇索引和非聚簇索引呢!
首先看一下Innodb引擎下,建立表生成的文件,能夠看到有倆個ibd文件。
看到這裏不知道你們有沒有疑問,爲何看有的文章中也會有frm文件呢!可是在這裏怎麼沒有呢!
緣由是在MySQL8.0以後將源數據都存儲到了表空間中,因此也就不存在frm文件嘍!
都知道這個idb文件會存儲數據信息和索引信息。
那再來看一下Myisam存儲引擎建立表生產的文件。
從圖中能夠看到建立一個表會生成三個文件,擴展名分別爲MYD、MYI、sdi。
MYD:是表數據文件(保存數據的文件)
MYI :是表索引文件(保存索引的文件)
那麼就能夠得出一個結論
只要數據跟索引存儲在一個文件裏,那就是聚簇索引,不然就是非聚簇索引。
這個時候就會有人問了,表中有主鍵的時候,idb文件中存儲的是主鍵+數據,那麼當沒有設置主鍵時怎麼辦呢!
記住這一句話,在Innodb中,數據插入時必須跟一個索引值進行綁定,若是沒有主鍵那就選擇惟一索引,若是沒有惟一索引就會選擇一個6Byte的rowid。
看了上文的解釋,有沒有產生過一絲疑問,在Innodb存儲引擎下,若是存在多個索引,是否是會產生多個idb文件。
在Innodb中數據只會保存一份,若是有多個索引,會維護多個B+Tree
例如:表字段 id,name,age,sex。
id設置爲主鍵索引(聚簇索引),name設置爲普通索引,那麼數據到底會存儲幾份呢!
無論一個表中設置多少個索引,數據只會存儲一份,可是這張表會維護多個B+Tree。
按照這個案例中id爲主鍵索引,name爲普通索引,那麼在這張表中就會維護倆顆B+Tree。
id主鍵索引跟數據存儲在一塊兒,name索引所在的B+Tree中葉子節點存儲的是主鍵id的值。
對應的圖就是如下倆幅圖,能夠好好的看一下。
最後給你們總結一個點:在Innodb中,必定有聚簇索引,其它索引都是非聚簇索引。
這裏簡單提一下myisam中只有非聚簇索引。
在面試中每每會問這幾個關鍵詞,分別爲回表、覆蓋索引、最左側原則、索引下推,必定要知道哈!
網上對回表的解釋各類各樣,咔咔給你說種簡單易懂的,但前提是你須要把聚簇索引、非聚簇索引區分清楚。
仍是用上邊的案例,id爲主鍵索引,name爲普通索引。
此時查詢語句爲select id,name,age from table where name = 'kaka'
那麼這條語句會先在name的這顆B+Tree中尋找到主鍵id,而後在根據主鍵id的索引獲取到數據而且返回。
其實這個過程就是從非聚簇索引跳轉到聚簇索引中查找數據,被稱爲回表
,也就是說當你查詢的字段爲非聚簇索引,可是非聚簇索引中沒有將須要查詢的字段所有包含就是回表。
在這個案例中,非聚簇索引name的葉子節點只有id,並無age,因此會跳轉到聚簇索引中,根據id在查詢整條記錄返回須要的字段數據。
覆蓋索引,根據名字都能理解的差很少,就是查詢的全部字段都建立了索引!
此時查詢語句爲select id,name from table where name = 'kaka'
那麼這條語句就是使用了覆蓋索引,由於id和name都爲索引字段,查詢的字段也是這倆個字段,因此被稱爲索引覆蓋。
也就是說當非覆蓋索引的葉子節點中包含了須要查詢的字段時就被稱爲覆蓋索引
最左匹配原則是在組合索引中存在的。
仍是用以前表信息:表字段 id,name,age,sex。
此時給name,age設置成組合索引。
如下語句中那個不符合最左側原則。
select * from table where name = ? and age = ?
select * from table where name = ?
select * from table where age = ?
select * from table where age= ? and name= ?
能夠自行作一下測驗哈!是隻有第三條語句不會用到索引,其它的三條語句都會符合最左側原則。
關於這個最左側原則遠遠不止這麼簡單的,一試就是一個坑,關於這部份內容咔咔後期會在優化文章中提到。
仍是使用這條sql語句。
select * from table where name = ? and age = ?
索引下推是在MySQL5.6及之後的版本出現的。
以前的查詢過程是,先根據name在存儲引擎中獲取數據,而後在根據age在server層進行過濾。
在有了索引下推以後,查詢過程是根據name、age在存儲引擎獲取數據,返回對應的數據,再也不到server層進行過濾。
當你使用Explain分析SQL語句時,若是出現了Using index condition
那就是使用了索引下推,索引下推是在組合索引的狀況出現概率最大的。
索引的數據文件是存儲在磁盤中的,也是須要進行持久化操做。
可是當使用索引時會把數據從磁盤讀取到內存中,讀取方式爲分塊讀取。
這時就要涉及到操做系統的概念,操做系統在磁盤中獲取數據,假設如今要取的數據大小是1kb,但操做系統並不會只取出你須要的這1kb,而是會取出4kb的數據。
爲何會是4kb,由於在操做系統中一頁的數據就是4kb。
那又爲何只須要1kb而取出整頁的數據呢!
那就又會涉及到另外一個概念那就是局部性原理
:數據和程序都有彙集成羣的傾向,在訪問了一條數據以後,在以後有極大的可能再次訪問這條數據和這條數據的相鄰數據。
因此說MySQL的Innodb存儲引擎,在讀取數據時也會採起這種局部性原理,每次讀取的數據是16kb。
在Innodb存儲引擎下每頁的大小默認爲16kb,這個參數也能夠進行調整,參數爲innodb_page_size。
最後一點:
既然標題問的是索引數據存儲在什麼地方,在第一句就直接回答了索引是存儲在磁盤中,而且以頁爲單位進行從磁盤往內存讀取。
那爲何不直接存儲在內存中呢!你有沒有這個疑問呢!
若是索引數據只存儲在內存中,那麼當電腦關機,服務器宕機以後,就須要從新生成索引,這種的效率是十分低的。
以上就是咔咔對索引的理解,在盡最大的可能將知識點說全面。
若是還有遺漏,或者文章中有錯誤的地方還請各位能給出提議。