共計 6905 個字符,預計需要花費 18 分鐘才能閱讀完成。
自動寫代碼機器人,免費開通
這篇文章將為大家詳細講解有關 InnoDB 的數據存儲文件和 MyISAM 的不同有哪些,丸趣 TV 小編覺得挺實用的,因此分享給大家做個參考,希望大家閱讀完這篇文章后可以有所收獲。
為什么需要建立索引
首先,我們都知道建立索引的目的是為了提高查詢速度,那么為什么有了索引就能提高查詢速度呢?
我們來看一下,一個索引的示意圖。
如果我有一個 SQL 語句是:select * from Table where id = 15 那么在沒有索引的情況下其實是會進行全表掃描的,就是挨個去找,直到找到 id=15 的這條記錄,時間復雜度是 O(n);
如果在有索引的情況下去進行查詢呢。首先會根據 id=15,在索引值里面進行二分查找,二分查找的效率是很高的,它的時間復雜度是 O(logn);
這就是索引為什么能提高查詢效率了,但是索引數據的量也是比較大的,所以一般并不是存儲在內存中的,都是直接存儲在磁盤中的,所以對磁盤中的文件內容進行讀取,免不了要進行磁盤 IO。
MySQL 的索引為什么使用 B +Tree
上面我們也說了,索引數據一般是存儲在磁盤中的,但是計算數據都是要在內存中進行的,如果索引文件很大的話,并不能一次都加載進內存,所以在使用索引進行數據查找的時候是會進行多次磁盤 IO,將索引數據分批的加載到內存中,因此一個好的索引的數據結構,在得到正確的結果前提下,一定是磁盤 IO 次數最少的。
Hash 類型
目前 MySQL 其實是有兩種索引數據類型可以選擇的,一個是 BTree(實際是 B +Tree)、一個 Hash。
但是為什么在實際的使用過程中,基本上大部分都是選擇 BTree 呢?
因為如果使用 Hash 類型的索引,MySQL 在創建索引的時候,會對索引數據進行一次 Hash 運算,這樣根據 Hash 值就能快速的定位到磁盤指針了,就算數據量很大,也能快速精準的定位到數據。
但是像 select * from Table where id 15 這種范圍查詢,Hash 類型的索引就搞不定了,對這種范圍查詢,會直接全表掃描,另外 Hash 類型的索引也搞不定排序。
還有就是雖然 MySQL 底層做了一系列的處理,但還是不能完全的保證,不產生 Hash 碰撞。
二叉樹
那 MySQL 為什么沒有二叉樹作為它的索引數據結構呢?我們都知道,二叉樹是通過二分查找來進行定位數據的,所以效果還是不錯的,時間復雜度是 O(logn);
但是二叉樹有個問題,就是在特殊情況下,它會退化成一根棍子,也就是一個單向鏈表。這個時候,它的時間復雜度就會退化成 O(n);
所以當我們要查詢 id=50 的記錄時,其實和全表掃描是一樣的了。所以因為存在這種情況,二叉樹不適合作為索引的數據結構。
平衡二叉樹
那么既然二叉樹,在特殊情況下會退化成鏈表,那么平衡二叉樹為什么不可以呢?
平衡二叉樹的子節點高度差不能超過 1,像下圖中的二叉樹,關鍵字為 15 的節點,它的左子節點高度為 0,右子節點高度為 1,高度差不超過 1,所以下面這棵樹是一棵平衡二叉樹。
因為能保持平衡,所以它的查詢時間復雜度為 O(logN),至于怎么保持平衡的,主要是做一些左旋,右旋等,具體保持平衡的細節不是本文主要內容,想了解的可自行搜索。
用這個數據結構來做 MySQL 的索引會有 什么問題呢?
磁盤 IO 過多:在 MySQL 當中,一次 IO 操作只讀取一個節點,那么一個節點若是最多就兩個子節點的話,那么就只有這兩個子節點的查詢范圍,所以要精確到具體的數據時,就需要進行多次讀取,如果樹非常深的話,那么將會進行大量的磁盤 IO。性能自然下降了。
空間利用率低:對于平衡二叉樹來說,每個節點值保存一個關鍵字,一個數據區,兩個子節點的指針。這樣導致了,一次辛辛苦苦的 IO 操作就只加載這么點數據,實在是有點殺雞用牛刀了。
查詢效果不穩定:如果在一個高度很深的平衡二叉樹中,若是查詢的數據正好是根節點,那么就會很快的查到,若是查詢的數據正好是葉子節點,那么會進行多次磁盤 IO 后才能返回,響應時間有可能和根節點的不在一個數量級上。
雖然說二叉樹解決的平衡的問題,但是也帶來了新的問題,那就是由于它本身樹的深度的,會造成一系列的效率問題。
那么為了解決平衡二叉樹的這類問題,平衡多叉樹(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 是如何進行查找數據的:
若是查詢 id= 7 的數據,先將關鍵字 20 的節點加載進內存,判斷出 7 比 20 小;
那么加載第一個子節點,若查詢的數據等于 12 或 17 則直接返回,不等于就繼續向下找,發現 7 小于 12;
那么繼續加載第一個子節點中去,找到 7 之后,直接將 7 下面的 data 數據返回。
這樣整個操作其實進行了 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= 2 存在于根節點,因為是左閉合區間存儲數據,所以 id = 2 的都在根節點的第一個子節點上;
那么取出第一個子節點,加載到內存中,發現當前節點存在 id= 2 的關鍵字,并且已經到了葉子節點了,那么直接取出葉子節點中的數據返回。
現在來看一下 B -Tree 和 B +Tree 的區別
B+Tree 的查詢采用的左閉合區間,這樣能更好的支持了自增索引的查詢效果,所以一般在創建主鍵的時候通常都是自增的。這一點和 B -Tree 是不一樣的。
B+Tree 中的根節點和分支節點上是不保存數據的,關鍵字相關的數據只保存在葉子節點上,這樣保證了查詢效果的穩定,任何查詢都要走到葉子節點才能獲取數據。而 B -Tree 在分支節點中保存了數據,若是命中關鍵字則直接返回數據。
B+Tree 的葉子節點是順序排列的,并且相鄰的兩個葉子節點中具有順序引用的關系,這樣能更好的支持了范圍查詢。而 B -Tree 是沒有這個順序關系的。
MySQL 的索引為什么選擇了 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 %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 引擎類型的表的文件差異。
這幾個文件每個都是有自己的作用:
InnoDB 引擎的表文件,一共有兩個:
*.frm 這類文件是表的定義文件。
*.ibd 這類文件是數據和索引存儲文件。表數據和索引聚集存儲,通過索引能直接查詢到數據。
MyIASM 引擎的表文件,一共有三個:
*.frm 這類文件是表的定義文件。
*.MYD 這類文件是表數據文件,表中的所有數據都保存在此文件中。
*.MYI 這類文件是表的索引文件,MyISAM 存儲引擎的索引數據單獨存儲。
MyISAM 數據存儲引擎,索引與數據的存儲結構
MyISAM 存儲引擎在存儲索引的時候,是將索引數據單獨存儲,并且索引的 B +Tree 最終指向的是數據存在的物理地址,而不是具體的數據。然后再根據物理地址去數據文件(*.MYD)中找到具體的數據。
如下圖所示:
那么當存在多個索引時,多個索引都指向相同的物理地址。
如下圖所示:
通過這個結構,我們可以看出來,MyISAM 的存儲引擎的索引都是同級別的,主鍵和非主鍵索引結構和查詢方式完全一樣。
InnoDB 數據存儲引擎,索引與數據的存儲結構
首先 InnoDB 的索引分為聚簇索引和非聚簇索引,聚簇索引即保存關鍵字又保存數據,在 B +Tree 的每個分支節點上保存關鍵字,葉子節點上保存數據。
“聚簇”的意思是數據行被按照一定順序一個個緊密地排列在一起存儲。一個表只能有一個聚簇索引,因為在一個表中數據的存放方式只有一種,一般是主鍵作為聚簇索引,如果沒有主鍵,InnoDB 會默認生成一個隱藏的列作為主鍵。
如下圖所示:
非聚簇索引,又稱為二級索引,雖然也是在 B +Tree 的每個分支節點上保存關鍵字,但是葉子節點不是保存的數據,而是保存的主鍵值。通過二級索引去查詢數據會先查詢到數據對應的主鍵,然后再根據主鍵查詢到具體的數據行。
如下圖所示:
由于非聚簇索引的設計結構,導致了,非聚簇索引在查詢的時候要進行兩次索引檢索,這樣設計的好處,可以保證了一旦發生數據遷移的時候,只需要更新主鍵索引即可,非聚簇索引并不用動,而且也規避了像 MyISAM 的索引那樣存儲物理地址,在數據遷移的時候的需要重新維護所有索引的問題。
總結
這次把 MySQL 的索引的數據結構,以及文件存儲結構,總結清楚了,后面在實際的工作過程中,設計索引的時候能夠考慮的更全了,通過了解了索引的數據結構,也能讓自己在實際寫 SQL 的時候,能考慮到哪些情況走索引哪些不走索引了。
MySQL 使用 B +Tree 作為索引的數據結構,因為 B +Tree 的深度低,節點保存的關鍵字多,磁盤 IO 次數少,從而保證了查詢效率更高。
B+Tree 能夠保證 MySQL 無論是主鍵索引還是非主鍵索引的查詢效果都是穩定的,每次都要查詢到葉子節點才能返回數據,B+Tree 的葉子節點的深度是一樣的,而且為了更好的支持自增主鍵,B+Tree 的查詢節點范圍是左閉合右開放。
MySQL 的 MyISAM 存儲引擎,表數據和索引數據是分別放到兩個文件中進行存儲的,由于它本身的索引的 B +Tree 的葉子節點指向的表數據所在的磁盤地址,而且索引沒有主鍵和非主鍵之分,所以分開存儲,能夠更好的統一管理索引;
MySQL 的 InnoDB 存儲引擎,表數據和索引數據是存儲在一個文件中的,因為 InnoDB 的聚簇索引的葉子節點指向的具體的數據行,而且為了保證查詢效果的穩定,InnoDB 表中必須要有一個聚簇索引,二級索引在進行索引檢索時,會先通過二級索引檢索到數據的主鍵值,再根據主鍵去聚簇索引中檢索到具體的數據。
關于“InnoDB 的數據存儲文件和 MyISAM 的不同有哪些”這篇文章就分享到這里了,希望以上內容可以對大家有一定的幫助,使各位可以學到更多知識,如果覺得文章不錯,請把它分享出去讓更多的人看到。
向 AI 問一下細節