MySQL教學欄位介紹的索引為什麼用B Tree
##前言
這篇文章的題目,是我真實在面試過程中遇到的問題,某網路群眾募資公司在檢視面試者MySQL相關知識的第一個問題,我當時還是比較懵的,沒想到這年輕人不講武德,不按套路出牌,一般的問MySQL的相關知識的時候,不都是問索引優化以及索引失效等相關問題嗎?怎麼還出來了,儲存檔案的不同?就算考察個MVCC機制也行啊。所以這次我就好好總結總結這部分知識點。為什麼需要建立索引
首先,我們都知道建立索引的目的是為了提高查詢速度,那麼為什麼有了索引就能提高查詢速度呢? 我們來看一下,一個索引的示意圖。
如果我有一個SQL語句是:
select * from Table where id = 15 那麼在沒有索引的情況下其實是會進行全表掃描的,就是挨個去找,直到找到id=15的這條記錄,時間複雜度是O(n);
MySQL的索引為什麼要使用B Tree
上面我們也說了,索引資料一般是儲存在磁碟中的,但是計算資料都是要在記憶體中進行的,如果索引檔案很大的話,並不能一次都載入進內存,所以在使用索引進行資料查找的時候是會進行多次磁碟IO,將索引資料分批的載入到記憶體中,因此一個好的索引的資料結構,在得到正確的結果前提下,一定是磁碟IO次數最少的。 <strong></strong>
Hash型別
目前MySQL其實是有兩種索引資料型別可以選擇的,一個是BTree(實際上是B Tree)、一個Hash。 但是為什麼在實際的使用過程中,基本上大部分都是選擇BTree呢? 因為如果使用Hash類型的索引,MySQL在創建索引的時候,會對索引資料進行一次Hash運算,這樣根據Hash值就能快速的定位到磁碟指標了,就算資料量很大,也能快速精準的定位到數據。這種範圍查詢,Hash類型的索引就搞不定了,對這種範圍查詢,會直接全表掃描,另外Hash類型的索引也搞不定排序。
二元樹
那MySQL為什麼沒有二元樹作為它的索引資料結構呢?我們都知道,二元樹是透過二分查找來進行定位資料的,所以效果還是不錯的,時間複雜度是O(logn);
但是二元樹有個問題,就是在特殊情況下,它會退化成一根棍子,也就是單向鍊錶。這時候,它的時間複雜度就會退化成O(n);
所以當我們要查詢id=50的記錄時,其實跟全表掃描是一樣的了。所以因為有這種情況,二元樹不適合作為索引的資料結構。
平衡二元樹
那麼既然二元樹,在特殊情況下會退化成鍊錶,那麼平衡二元樹為什麼不行呢?平衡二元樹的子節點高度差不能超過1,像下圖中的二元樹,關鍵字為15的節點,它的左子節點高度為0,右子節點高度為1,高度差不超過1,所以下面這棵樹是一棵平衡二元樹。
因為能保持平衡,所以它的查詢時間複雜度為O(logN),至於怎麼保持平衡的,主要是做一些左旋,右旋等,具體保持平衡的細節不是本文主要內容,想了解的可自行搜尋。
用這個資料結構來做MySQL的索引會有 什麼問題呢?
雖然說二元樹解決的平衡的問題,但是也帶來了新的問題,那就是由於它本身樹的深度的,會造成一系列的效率問題。
那麼為了解決平衡二元樹的這類問題,平衡多叉樹(Balance Tree)就成為了更好的選擇。
平衡多叉樹(Balance Tree–B-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的變種,B Tree的每個節點關鍵字和m階的公式關係和B-Tree的不一樣了。
首先每個節點的子節點數量和每個節點可儲存的關鍵字比例是1:1
,其次就是查詢資料的時候採用的是左閉合區間進行查詢,還有就是分支節點中沒有資料了只保存關鍵字和子節點指向,資料都儲存在葉子節點。
那麼來看看在B Tree中是如何進行資料查詢的。
例如:
id=2
存在於根節點,因為是左閉合區間儲存數據,所以id的都在根節點的第一個子節點上;
id=2
的關鍵字,而且已經到了葉子節點了,那麼直接取出葉子節點中的資料回傳。 現在來看B-Tree和B Tree的區別
經過上面的層層分析,現在我們可以總結一下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作為了它的索引的資料結構。
InnDB的資料儲存檔案和MyISAM的有何不同?
上面總結了MySQL的索引的資料結構,這次就可以說第二個問題了,因為這個問題其實和MySQL的索引還是有一定的關係的。
下面來看看,先找到伺服器桑MySQL儲存資料的目錄:
登入MySQL,開啟MySQL的命令列介面:輸入show variables like '�tadir%';
,就能看到儲存資料的目錄了。
我的伺服器中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資料儲存引擎,索引與資料的儲存結構
MyISAM儲存引擎在儲存索引的時候,就是將索引資料是單獨存儲,並且索引的B Tree最終指向的是資料存在的實體位址,而不是特定的資料。然後再根據實體位址去資料檔(*.MYD)中找到具體的資料。
如下圖所示:
那麼當存在多個索引時,多個索引都指向相同的實體位址。
如下圖:
透過這個結構,我們可以看出來,MyISAM的儲存引擎的索引都是同層級的,主鍵和非主鍵索引結構和查詢方式完全一樣。
InnoDB資料儲存引擎,索引與資料的儲存結構
首先InnoDB的索引分為叢集索引和非叢集索引,叢集索引即保存關鍵字又保存數據,在B Tree的每個分支節點上保存關鍵字,葉子節點上保存數據。
“聚簇”的意思是資料行被依照一定順序一個個緊密地排列在一起儲存。一個表格只能有一個叢集索引,因為在一個表格中資料的存放方式只有一種,一般是主鍵作為叢集索引,如果沒有主鍵,InnoDB會預設產生一個隱藏的資料列作為主鍵。
如下圖所示:
非聚集索引,又稱為二級索引,雖然也是在B Tree的每個分支節點上保存關鍵字,但是葉子節點不是保存的數據,而是保存的主鍵值。透過二級索引去查詢資料會先查詢到資料對應的主鍵,然後再根據主鍵查詢到特定的資料行。
如下圖所示:
由於非聚集索引的設計結構,導致了,非叢集索引在查詢的時候要進行兩次索引檢索,這樣設計的好處,可以保證了一旦發生資料遷移的時候,只需要更新主鍵索引即可,非聚簇索引並不用動,而且也規避了像MyISAM的索引那樣存儲物理地址,在資料遷移的時候的需要重新維護所有索引的問題。
總結
這次把MySQL的索引的資料結構,以及檔案儲存結構,總結清楚了,後面在實際的工作過程中,設計索引的時候能夠考慮的更全了,透過了解了索引的資料結構,也能讓自己在實際寫SQL的時候,能考慮到哪些情況走索引哪些不走索引了。
相關免費學習推薦:mysql影片教學
以上是InnoDB的資料儲存檔案和MyISAM的不同的詳細內容。更多資訊請關注PHP中文網其他相關文章!