從底層解析B+索引提高查詢速度的原因

前言

我的上篇文章《解析B+樹比B樹更加適合做數據庫索引的原因》介紹了爲什麼B+樹更適合做索引,並以InnoDB和MyISAM存儲引擎介紹了聚集索引和非聚集索引的區別。Mysql默認的存儲引擎是InnoDB,因此此篇文章主要以InnoDB的數據頁結構以及數據頁結構之間的連接方式和查找過程來說明B+索引提高查詢速度的原因。

數據頁結構

Mysql進行數據存儲的基本結構是數據頁(也可稱作塊),一頁的大小一般是16K。Mysql表中的記錄都是存儲在數據頁中。InnoDB數據頁由以下7個部分組成,每個部分都有着不同的功能,並且每個部分又分爲若干個小部分,小部分這裏就不詳細介紹,主要介紹下7個部分的功能,爲下文的講解做下鋪墊。
在這裏插入圖片描述
(1)File Header: 表示文件頭,佔固定的38字節。記錄了包括頁的槽數量、頁號以及頁在樹中的位置等衆多信息,且裏面含有上一個頁號,和下一個頁號,以此在各個數據頁之間構成了雙向鏈表。
在這裏插入圖片描述
(2)Page Header:表示頁裏的一些狀態信息,佔固定的56個字節。

(3)Infimum + Supremum:兩個虛擬的僞記錄,分別表示頁中的最小和最大記錄,佔固定的26個字節。

(4)User Records: 存儲我們插入的記錄的部分,大小不固定。然後每一個記錄除了真實的數據,還有記錄頭,用以記載記錄的類型以及下一條記錄等信息。
在這裏插入圖片描述
(4.1)記錄頭中的next_record屬性值爲從當前記錄的真實數據到下一條記錄的真實數據的地址偏移量。比方說第一條記錄的next_record值爲13,意味着從第一條記錄的真實數據的地址處向後找13個字節便是下一條記錄的真實數據,這裏的下一條記錄是以主鍵值順序大小排序的下一條記錄,記錄與其下一條記錄在物理上並不一定是緊挨着的,因此頁內的各個記錄之間形成的其實是個單鏈表。
(4.2)記錄頭中的record_type表示當前記錄的類型,一共有4種類型的記錄,0表示普通記錄,1表示B+樹非葉結點記錄(目錄項記錄),2表示最小記錄,3表示最大記錄。
(4.3)n_owned 指的是該槽所指向的組內的記錄的個數。最小記錄所在的分組只能有 1 條記錄,最大記錄所在的分組擁有的記錄條數只能在 1-8 條之間,剩下的分組中記錄的條數範圍只能在是 4~8 條之間。

(5)Free Space: 頁中尚未使用的部分,大小不確定。每當我們插入一條記錄,都會從Free Space部分,也就是尚未使用的部分中申請一個記錄大小的空間劃分到User Records部分,當Free Space部分的空間全部被User Records部分替代掉之後,也就意味着這個頁使用完了,如果還有新的記錄插入的話,就需要去申請新的頁了。

(6)Page Directory: 頁目錄,由槽組成,槽也就是每一組最後一個記錄的地址偏移量,我們可通過該地址偏移量找到該組最後一個記錄。頁目錄大小不固定,插入的記錄越多,這個部分佔用的空間越多。
(6.1)頁目錄的作用: 頁內的記錄以單鏈表的結構進行連接,那我們在頁內查找記錄豈不是隻能從最小記錄來挨個進行遍歷?當頁內記錄過多時,那效率豈不是很低?當然不得行!InnoDB可通過頁目錄以主鍵值進行快速查找。InnoDB將若干個記錄分爲一組,目錄頁的槽中記錄每組最後一個記錄的位移偏移量和主鍵值。當我們進行記錄查找時,先在槽中對槽編號進行二分查找,然後比較要查找的記錄的主鍵值和該槽中的主鍵值,若不相等,則繼續進行二分查找,直到找到我們需要查找的記錄所在的槽或者槽範圍,然後通過位移偏移量找到該組中的最後一個記錄,通過該記錄的next_record進行組內遍歷即可找到該記錄。

以下圖進行具體查找示例說明: 比如我要查找主鍵值爲11的記錄
圖中有5個槽,槽編號爲0~4,第一步對槽編號進行二分查找,初始時,low=0,high=4,因此mid=(0+4)/2=2,所以先進入槽2進行主鍵值比較,8<11,所以low=mid=2,high=4,此時mid=3,進入槽3進行主鍵值比較,12>11,因此low=2,high=3,high=low-1,因此確定記錄在槽3中,拿着槽2中的位移偏移量找到該組中的最後一個記錄,通過該記錄的next_record開始對槽3對應的組內記錄進行遍歷,即可找到該記錄。

(7)File Trailer:用於檢驗頁是否完整的部分,佔用固定的8個字節。爲保證從內存中同步到磁盤的頁的完整性,在頁的首部和尾部都會存儲頁中數據的校驗和和LSN值,如果首部和尾部的校驗和和LSN值校驗不成功的話,就說明同步過程出現了問題。

小結

1、沒有使用索引情況下,主鍵查找比其它字段查找快的原因,是因爲其可通過頁目錄進行二分查找查找到該記錄所在的分組,然後再到該分組中進行遍歷即可找到記錄,其它字段只能在數據頁中對記錄進行依次遍歷。
2、數據庫中的記錄是以數據頁爲基本單位進行存儲的,數據頁之間是以主鍵的大小爲順序通過雙鏈表進行連接,A數據頁的所有主鍵大小必然小於其下一頁的所有主鍵大小,數據頁內的記錄也是以主鍵大小順序通過單鏈表進行連接,A記錄的主鍵值小於其下一記錄的主鍵值。

沒有使用索引時,記錄的查找過程

1、記錄的查找,包括兩個階段,分別是頁的定位、以及在頁中記錄的定位。
2、數據頁之間是通過雙向鏈表進行連接。我們定位記錄所在的頁時,需要從第一數據頁開始遍歷,依次遍歷雙向鏈表中的每一頁中是否存在該記錄,直至找到該頁。
3、在頁中具體的查找記錄的過程分爲兩種:
通過主鍵進行查找: 對頁目錄進行二分查找,找到該分組,然後在組內進行遍歷,速度較快。
其它非主鍵字段查找: 在頁內的記錄單鏈表從頭開始遍歷,直至找到該關鍵字。

因此當數據量很大、頁表很多時,那I/O等開銷就非常的大。

使用B+索引提高查詢速度的原因

InnoDB中B+索引能提高查詢速度的原因其實就是通過引進B+樹的結構將頁的定位過程進行了優化。

B+索引的結構

B+索引中數據頁分爲兩種,一種是目錄數據頁,另外一種就是普通的用戶記錄數據頁。目錄數據頁中每條記錄都是一個目錄項記錄,目錄項記錄與數據記錄沒什麼太大區別,除了該記錄的record_type爲1,目錄項列信息只包括所指向的數據頁中最小的主鍵值和所指向的數據頁的頁號。InnoDB中每個非葉子結點都是一個目錄數據頁,而每個葉子結點都是一個用戶記錄數據頁,只有葉子結點才存儲實際的用戶記錄,該記錄的record_type爲0,記錄的列信息中包括實際字段值。非葉子結點(目錄項數據頁)的目錄項記錄起着指針的作用。
在這裏插入圖片描述
以上圖中,頁33是根目錄數據頁,頁中的目錄項30,目錄項32分別指向目錄數據頁30和目錄數據頁32,然後目錄數據頁中的目錄項再指向用戶記錄數據表。
(說明: 數據頁的編號可能並不是連續的,這也是以上兄弟結點編號不相鄰的原因。)

使用B+索引,查找過程

以上圖爲例,比如我要查找主鍵值爲220的記錄。

(1)先在根目錄數據頁(頭結點)中,進行目錄項確定。通過頁內的目錄表進行二分查找,找到該分組,在組內進行遍歷,找到該主鍵,關鍵字1<220<320,第二個目錄項所指向的數據頁中最小的關鍵字都是320,因此我們要找的目錄項肯定是第一塊。
(2)我們通過根目錄數據頁的第一塊目錄項,拿到頁號30,然後到數據頁表30中進行目錄項確定。在頁30的目錄表進行二分查找,找到該分組,在組內進行遍歷,220>209,因此我們取得數據頁表30的最後一個目錄項記錄。
(3)我們拿着該記錄中的頁號20,去數據頁表20中進行記錄查找。數據頁表20是用戶記錄頁表,也就是葉子結點,存儲着用戶記錄。通過頁內的目錄表進行二分查找,找到記錄所在的分組,在組內進行遍歷,最終找到了關鍵字220的那一個記錄。

小結

通過比較使用B+索引和不使用B+索引的查找過程,我們可以發現即使是主鍵查找,建立索引和不建立索引的性能也是有着很大的差別。例如一個擁有着1000000000條記錄的表,每個數據頁可以存放1000條記錄。1000000000=1000✖1000✖1000,構成的B+樹只有三層。我使用B+索引只需要3次頁的查找就能找到所查詢的記錄所在的頁。而不使用B+索引,則可能要查找1000000個頁才能找到該記錄所在的頁。這樣就是說我們可能得做1000000的I/O操作。而非主鍵查找,不建立索引,我們只能一頁頁的遍歷,頁內也是一個記錄一個記錄的依次查找。因此B+索引很大程度上提高了查詢速度,當然天下沒有免費的午餐,快查詢帶來的代價就是我們在增刪改數據時,可能會改變B+樹的結構,因此需要動態的維護着B+樹的結構,這就降低了增刪改的速度。因此B+索引通常建立在常用來做查詢條件或者排序的字段之上。

拓展(必看)

申明:覺得這部分聚集索引和非聚集索引沒看懂的話可以看我另外一篇帶有圖的、更具體的聚集索引和非聚集索引區別講解《解析B+樹比B樹更加適合做數據庫索引的原因

本篇文章主要是討論B+索引提高查詢速度的原因,但不知道讀者有沒有發現,我以上討論的其實都是聚集索引,InnoDB使用聚集索引,默認是建立在主鍵之上。InnoDB在插入數據時其實就是以聚集索引的值的大小順序進行存儲,若不滿足大小順序則需要對數據進行移動。例如數據表中已經插入主鍵值爲1、3、5、7、9的數據(存儲順序以主鍵值大小順序進行存儲),此時我要插入主鍵值爲4的數據記錄,此時磁盤上數據的存儲順序就變成了1、3、4、5、7、9,這也是聚集索引修改慢的原因,插入數據可能造成數據的移動,需要對數據頁進行重排序,開銷很大,所以一般採取主鍵自增。聚集索引的葉子結點(用戶記錄數據表)中存儲了完整的用戶記錄(包括所有的列值),因此就不需要進行回表。InnoDB的數據文件本身就是索引,數據和索引一起存在一個XX.IDB文件中。

但我們不可能所有的查詢條件和排序條件都是主鍵,因此我們需要使用到非聚集索引,一個表中可以有多個非聚集索引。

非聚集索引

非聚集索引中索引值的邏輯順序與磁盤上的物理存儲順序不同,一個表中可以擁有多個非聚集索引。數據是以聚集索引值大小順序進行存儲,但聚集索引只在搜索條件是主鍵的時候才起作用,因此我們常常使用到非聚集索引,那麼如何使用呢?我們需要以非聚集索引值建立一顆新的B+樹,比如主鍵爲A1,我們經常以字段A2爲條件進行查找,那可以在A2上建立一個非聚集索引,以A2值爲關鍵字建立一棵B+樹。該B+樹有以下特點:頁內的記錄是按照A2列的大小順序排成一個單向鏈表;各個存放用戶記錄的頁也是根據頁中記錄的A2列大小順序排成一個雙向鏈表;各個存放目錄項的頁也是根據頁中記錄的A2列大小順序排成一個雙向鏈表。當以A2爲條件進行查找時,就以A2值 在該B+樹上進查找,查找到後,若需要select的字段都存在葉結點中,則返回該值,若不在,則需要進行回表操作。

InnoDB回表操作-非聚集索引的葉子結點(用戶數據頁表)除了存儲了該記錄的非聚集索引值,還存儲了主鍵值,當該索引不是覆蓋索引時,需要通過主鍵值在聚集索引中查找到該具體記錄。

聯合索引

聯合索引指的是以多個列的大小作爲排序規則,同時在多個列上建立索引。例如爲A1、A2兩個字段建立聯合索引,在建立B+樹的過程中,我們以A1的大小順序進行樹的構建,若A1相同時,則以A2的大小爲順序進行樹的構建。且目錄數據頁表中每條目錄項記錄都由A1、A2、頁號這三個部分組成,各條記錄先按照A1列的值進行排序,如果記錄的A1列相同,則按照A2列的值進行排序。葉子結點(用戶記錄數據表)記錄了A1、A2、主鍵值。(當查找的字段不止A1、A2、主鍵值時,需要通過主鍵值去查聚集索引的B+樹來獲得完整用戶記錄)
在這裏插入圖片描述
最左匹配原則: 在使用聯合索引時,我們要注意最左匹配原則。當查詢條件和ORDER BY子句不滿足最左匹配原則時,索引未命中,進行全表掃描。最左匹配原則也叫最左前綴原則,如果查詢的時候查詢條件精確匹配索引的左邊連續一列或幾列,則索引命中。例如,建立聯合索引(name,age)

select * from user where name=xx and age=xx ; //可以命中索引,需要回表
select * from user where name=xx ;           / / 可以命中索引,需要回表
select * from user where age=xx ;           / / 無法命中索引,全表掃描  

覆蓋索引

如果一個索引包含了所有需要查詢的字段的值,我們就稱之爲“覆蓋索引”。這種索引避免了進行回表操作。比如我們在字段username和high上創建聯合索引。

select username , age from user where username = 'Java' and high=173

當執行以上sql語句時,這個聯合索引其實就是個覆蓋索引,不需要進行回表操作

冗餘索引

冗餘索引指的是多個索引的前綴列相同,或者在聯合索引中包含了主鍵的索引。例如一張表有索引(name)和索引(name,age)或者索引(name,age,high)三個索引,這三個索引就是冗餘索引。能命中(name,age,high)索引的查詢肯定能命中索引(name)和索引(name,age)。我們需要避免冗餘索引,當我們需要通過構建覆蓋索引時,最好是在已有的索引上面擴展,而不是新建索引。例如上例子中要用到索引(name,age)來避免回表操作,我們就通過在索引(name)上面進行擴展,加入age字段作爲索引的一部分。

重複索引

重複索引指的是在同一個列或者順序相同的幾個列(age,school),建立了多個索引,重複索引沒有任何幫助只會增大索引文件,影響更新速度。

MyISAM中的索引方案

MyISAM存儲引擎雖然默認使用的也是 B+索引,但它使用的是非聚集索引,數據和索引是分開的,索引放在XX.MYI文件中,數據放在XX.MYD文件中。MyISAM的表中數據是按照插入先後順序進行存儲的,不是以主鍵值的大小順序進行存儲的,因此我們無法進行二分查找.但每一個記錄都有一個行號,我們可以通過行號在數據文件中找到完整用戶記錄。MyISAM會爲表的主鍵創建B+索引,數據頁的每個記錄中不僅有主鍵值還有行號,通過主鍵值獲得行號,然後進行回表操作,用行號去數據文件中進行完整記錄的查找。
在這裏插入圖片描述

參考:
《Java工程師修煉之道》
《MySQL高性能書籍_第3版》
MySQL的索引

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章