這篇文章的題目,是我真實在面試過程當中遇到的問題,某互聯網衆籌公司在考察面試者MySQL相關知識的第一個問題,我當時仍是比較懵的,沒想到這年輕人不講武德,不按套路出牌,通常的問MySQL的相關知識的時候,不都是問索引優化以及索引失效等相關問題嗎?怎麼還出來了,存儲文件的不一樣?哪怕考察個MVCC機制也行啊。因此此次我就好好總結總結這部分知識點。mysql
首先,咱們都知道創建索引的目的是爲了提升查詢速度,那麼爲何有了索引就能提升查詢速度呢?
咱們來看一下,一個索引的示意圖。
若是我有一個SQL語句是:select * from Table where id = 15
那麼在沒有索引的狀況下實際上是會進行全表掃描的,就是挨個去找,直到找到id=15的這條記錄,時間複雜度是O(n);面試
若是在有索引的狀況下去進行查詢呢。首先會根據id=15,在索引值裏面進行二分查找,二分查找的效率是很高的,它的時間複雜度是O(logn);sql
這就是索引爲何能提升查詢效率了,可是索引數據的量也是比較大的,因此通常並非存儲在內存中的,都是直接存儲在磁盤中的,因此對磁盤中的文件內容進行讀取,免不了要進行磁盤IO。shell
上面咱們也說了,索引數據通常是存儲在磁盤中的,可是計算數據都是要在內存中進行的,若是索引文件很大的話,並不能一次都加載進內存,因此在使用索引進行數據查找的時候是會進行屢次磁盤IO,將索引數據分批的加載到內存中,所以一個好的索引的數據結構,在獲得正確的結果前提下,必定是磁盤IO次數最少的。
數據庫
目前MySQL實際上是有兩種索引數據類型能夠選擇的,一個是BTree(實際是B+Tree)、一個Hash。服務器
可是爲何在實際的使用過程當中,基本上大部分都是選擇BTree呢?微信
由於若是使用Hash類型的索引,MySQL在建立索引的時候,會對索引數據進行一次Hash運算,這樣根據Hash值就能快速的定位到磁盤指針了,就算數據量很大,也能快速精準的定位到數據。數據結構
select * from Table where id > 15
這種範圍查詢,Hash類型的索引就搞不定了,對這種範圍查詢,會直接全表掃描,另外Hash類型的索引也搞不定排序。那MySQL爲何沒有二叉樹做爲它的索引數據結構呢?咱們都知道,二叉樹是經過二分查找來進行定位數據的,因此效果仍是不錯的,時間複雜度是O(logn);
可是二叉樹有個問題,就是在特殊狀況下,它會退化成一根棍子,也就是一個單向鏈表。這個時候,它的時間複雜度就會退化成O(n);
因此當咱們要查詢id=50的記錄時,其實和全表掃描是同樣的了。因此由於存在這種狀況,二叉樹不適合做爲索引的數據結構。性能
那麼既然二叉樹,在特殊狀況下會退化成鏈表,那麼平衡二叉樹爲何不能夠呢?學習
平衡二叉樹的子節點高度差不能超過1,像下圖中的二叉樹,關鍵字爲15的節點,它的左子節點高度爲0,右子節點高度爲1,高度差不超過1,因此下面這棵樹是一棵平衡二叉樹。
由於能保持平衡,因此它的查詢時間複雜度爲O(logN),至於怎麼保持平衡的,主要是作一些左旋,右旋等,具體保持平衡的細節不是本文主要內容,想了解的可自行搜索。
用這個數據結構來作MySQL的索引會有 什麼問題呢?
雖說二叉樹解決的平衡的問題,可是也帶來了新的問題,那就是因爲它自己樹的深度的,會形成一系列的效率問題。
那麼爲了解決平衡二叉樹的這類問題,平衡多叉樹(Balance Tree)就成爲了更好的選擇。
B-Tree的意思是平衡多叉樹,通常B-Tree中的一個節點有多少個子節點,咱們就稱爲多少階的B-Tree。一般用m表示階數,當m爲2的時候,就是平衡二叉樹。
一棵B-Tree的每一個節點上最多能有m-1個關鍵字,最少要存放Math.ceil(m/2)-1
個關鍵字,全部的葉子節點都在同一層。以下圖就是一個4階的B-Tree。
那麼咱們看一下B-Tree是如何進行查找數據的:
這樣整個操做其實進行了3次IO操做,但實際上通常的B-Tree每層都是有不少分支(一般都大於100)。
MySQL爲了能更好的利用磁盤的IO能力,將操做頁的大小設置爲了16K,即每一個節點的大小爲16K。若是每一個節點中的關鍵字都是int類型的,那麼就是4個字節,若數據區的大小爲8個字節,節點指針再佔4個字節,那麼B-Tree的每一個節點中能夠保存的關鍵字個數爲:(16*1000) / (4+8+4)=1000
,每一個節點最多可存儲1000個關鍵字,每個節點最多能夠有1001個分支節點。
這樣在查詢索引數據的時候,一次磁盤IO操做能夠將1000個關鍵字,讀取到內存中進行計算,B-Tree的一次磁盤IO的操做,頂上平衡二叉數據的N次磁盤IO操做了。
要注意的是
:B-Tree爲了保證數據的平衡,會作一系列的操做,這個保持平衡的過程比較耗時間,因此在建立索引的時候,要選擇合適的字段,而且不要過多的建立索引,建立索引過多的話,在更新數據的時候,更新索引的過程也比較耗時。
還有就是不要選擇低區分度字段值做爲索引,例如性別字段,總共就兩個值,那麼就有可能會形成B-Tree的深度過大,索引效率下降。
B-Tree已經很好的解決平衡二叉樹的問題了,而且也能保證查詢效率了,那麼爲何會有B+Tree呢?
咱們先來B+Tree是什麼樣子的。
B+Tree是B-Tree的變種,B+Tree的每一個節點關鍵字和m階的公式關係和B-Tree的不同了。
首先每一個節點的子節點數量和每一個節點可存儲的關鍵字比例是1:1
,其次就是查詢數據的時候採用的是左閉合區間進行查詢,還有就是分支節點中沒有數據了只保存關鍵字和子節點指向,數據都存儲在葉子節點。
那麼來看一下在B+Tree中是如何進行數據查詢的。
例如:
id=2
存在於根節點,由於是左閉合區間存儲數據,因此id<=2
的都在根節點的第一個子節點上;id=2
的關鍵字,而且已經到了葉子節點了,那麼直接取出葉子節點中的數據返回。通過上面的層層分析,如今咱們能夠總結一下MySQL爲何選擇了B+Tree做爲它索引的數據結構呢。
首先和平衡二叉樹相比,B+Tree的深度更低,節點保存關鍵字更多,磁盤IO次數更少,查詢計算效率更好。
B+Tree的全局掃描能力更強,如果想根據索引數據對數據表進行全局掃描,B-Tree會將整棵樹進行掃描,而後逐層遍歷。而B+Tree呢,只須要遍歷葉子節點便可,由於葉子節點之間存在順序引用的關係。
B+Tree的磁盤IO讀寫能力更強,由於B+Tree的每一個分支節點上只保存了關鍵字,這樣每次磁盤IO在讀寫的時候,一頁16K數據量能夠存儲更多的關鍵字了,每一個節點上保存的關鍵字也比B-Tree更多了。這樣B+Tree的一次磁盤IO加載的數據比B-Tree的多不少了。
B+Tree數據結構中有自然的排序能力,比其餘數據結構排序能力更強並且排序時,是經過分支節點來進行的,如果須要將分支節點加載到內存中排序,一次加載的數據更多。
B+Tree的查詢效果更穩定,由於全部的查詢都是須要掃描到葉子節點纔將數據返回的。效果只是穩定而不必定是最優,如果直接查詢B-Tree的根節點數據,那麼B-Tree只須要一次磁盤IO就能夠直接將數據返回,反而是效果最優。
通過以上幾點的分析,MySQL最終選擇了B+Tree做爲了它的索引的數據結構。
上面總結了MySQL的索引的數據結構,此次就能夠說第二個問題了,由於這個問題其實和MySQL的索引仍是有必定的關係的。
下面來看一下,先找到服務器桑MySQL存儲數據的目錄:
登陸MySQL,打開MySQL的命令行界面:輸入show variables like '%datadir%';
,就能看到存儲數據的目錄了。
個人服務器中MySQL的存儲數據的目錄是在:
/var/lib/mysql/
進入到這個目錄裏後,能看到全部數據庫的目錄,新建一個study_test
的數據庫。
而後就進入
/var/lib/mysql/study_test
這個目錄下,目前就只有一個文件,這個文件是用來記錄建立數據庫時配置的字符集的內容。
-rw-r----- 1 mysql mysql 60 1月 31 10:28 db.opt
如今新建兩個表,第一個表的引擎類型選擇InnoDB,第二個表的引擎類型選擇MyISAM。
student_innodb:
CREATE TABLE `student_innodb` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `name` varchar(50) COLLATE utf8mb4_bin DEFAULT NULL, `age` int(11) DEFAULT NULL, `address` varchar(100) COLLATE utf8mb4_bin DEFAULT NULL, PRIMARY KEY (`id`), KEY `idx_name` (`name`) USING BTREE COMMENT 'name索引' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='innodb引擎表';
student_myisam:
CREATE TABLE `student_myisam` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `name` varchar(50) COLLATE utf8mb4_bin DEFAULT NULL, `age` int(11) DEFAULT NULL, `address` varchar(100) COLLATE utf8mb4_bin DEFAULT NULL, PRIMARY KEY (`id`), KEY `idx_name` (`name`) USING BTREE COMMENT 'name索引' ) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='myISAM引擎類型表';
將兩個表建立完成後,咱們再進入到/var/lib/mysql/study_test
看一下:
-rw-r----- 1 mysql mysql 60 1月 31 10:28 db.opt -rw-r----- 1 mysql mysql 8650 1月 31 10:41 student_innodb.frm -rw-r----- 1 mysql mysql 114688 1月 31 10:41 student_innodb.ibd -rw-r----- 1 mysql mysql 8650 1月 31 10:58 student_myisam.frm -rw-r----- 1 mysql mysql 0 1月 31 10:58 student_myisam.MYD -rw-r----- 1 mysql mysql 1024 1月 31 10:58 student_myisam.MYI
經過目錄中的文件可看到建立表以後多了幾個文件,這樣也看出來了,InnoDB引擎類型的表和MyISAM引擎類型的表的文件差別。
這幾個文件每一個都是有本身的做用:
MyISAM存儲引擎在存儲索引的時候,是將索引數據單獨存儲,而且索引的B+Tree最終指向的是數據存在的物理地址,而不是具體的數據。而後再根據物理地址去數據文件(*.MYD)中找到具體的數據。
以下圖所示:
那麼當存在多個索引時,多個索引都指向相同的物理地址。
以下圖所示:
經過這個結構,咱們能夠看出來,MyISAM的存儲引擎的索引都是同級別的,主鍵和非主鍵索引結構和查詢方式徹底同樣。
首先InnoDB的索引分爲聚簇索引和非聚簇索引,聚簇索引即保存關鍵字又保存數據,在B+Tree的每一個分支節點上保存關鍵字,葉子節點上保存數據。
「聚簇」的意思是數據行被按照必定順序一個個緊密地排列在一塊兒存儲。一個表只能有一個聚簇索引,由於在一個表中數據的存放方式只有一種,通常是主鍵做爲聚簇索引,若是沒有主鍵,InnoDB會默認生成一個隱藏的列做爲主鍵。
以下圖所示:
非聚簇索引,又稱爲二級索引,雖然也是在B+Tree的每一個分支節點上保存關鍵字,可是葉子節點不是保存的數據,而是保存的主鍵值。經過二級索引去查詢數據會先查詢到數據對應的主鍵,而後再根據主鍵查詢到具體的數據行。
以下圖所示:
因爲非聚簇索引的設計結構,致使了,非聚簇索引在查詢的時候要進行兩次索引檢索,這樣設計的好處,能夠保證了一旦發生數據遷移的時候,只須要更新主鍵索引便可,非聚簇索引並不用動,並且也規避了像MyISAM的索引那樣存儲物理地址,在數據遷移的時候的須要從新維護全部索引的問題。
此次把MySQL的索引的數據結構,以及文件存儲結構,總結清楚了,後面在實際的工做過程當中,設計索引的時候可以考慮的更全了,經過了解了索引的數據結構,也能讓本身在實際寫SQL的時候,能考慮到哪些狀況走索引哪些不走索引了。
微信公衆號:Jimoer