首先需要知道的是MySQL中都是是用B+樹來實現底層數據結構的。首先需要介紹一下B+樹。
B+樹介紹
如圖所示就是一顆B+樹,這里簡單介紹一下B+樹的結構和特點。圖中淺藍色的塊稱之為一個磁盤塊,其中每個磁盤塊中包含幾個數據項(深藍色塊,也叫關鍵字)和指針(黃色塊),如磁盤塊1包含數據項17和35,包含指針P1、P2、P3,P1表示小於17的磁盤塊,P2表示在17和35之間的磁盤塊,P3表示大於35的磁盤塊。真實的數據存在於葉子節點即3、5、9、10、13、15、28、29、36、60、75、79、90、99。非葉子節點只不存儲真實的數據,只存儲指引搜索方向的數據項,如17、35並不真實存在於數據表中。
B+樹的查找過程
如圖所示,如果要查找數據項29,那么首先會把磁盤塊1由磁盤加載到內存,此時發生一次IO,在內存中用二分查找確定29在17和35之間,鎖定磁盤塊1的P2指針,內存時間因為非常短(相比磁盤的IO)可以忽略不計,通過磁盤塊1的P2指針的磁盤地址把磁盤塊3由磁盤加載到內存,發生第二次IO,29在26和30之間,鎖定磁盤塊3的P2指針,通過指針加載磁盤塊8到內存,發生第三次IO,同時內存中做二分查找找到29,結束查詢,總計三次IO。真實的情況是,3層的b+樹可以表示上百萬的數據,如果上百萬的數據查找只需要三次IO,性能提高將是巨大的,如果沒有索引,每個數據項都要發生一次IO,那么總共需要百萬次的IO,顯然成本非常非常高。
B+樹性質
-
通過上面的分析,我們知道IO次數取決於b+數的高度h,假設當前數據表的數據為N,每個磁盤塊的數據項的數量是m,則有h=㏒(m+1)N,當數據量N一定的情況下,m越大,h越小;而m = 磁盤塊的大小 / 數據項的大小,磁盤塊的大小也就是一個數據頁(根據操作系統而定,4K、8K或16K)的大小,是固定的,如果數據項占的空間越小,數據項的數量越多,樹的高度越低。這就是為什么每個數據項,即索引字段要盡量的小,比如int占4字節,要比bigint8字節少一半。這也是為什么b+樹要求把真實的數據放到葉子節點而不是內層節點,一旦放到內層節點,磁盤塊的數據項會大幅度下降,導致樹增高。當數據項等於1時將會退化成線性表。
-
當b+樹的數據項是復合的數據結構,比如(name,age,sex)的時候,b+數是按照從左到右的順序來建立搜索樹的,比如當(張三,20,F)這樣的數據來檢索的時候,b+樹會優先比較name來確定下一步的所搜方向,如果name相同再依次比較age和sex,最后得到檢索的數據;但當(20,F)這樣的沒有name的數據來的時候,b+樹就不知道下一步該查哪個節點,因為建立搜索樹的時候name就是第一個比較因子,必須要先根據name來搜索才能知道下一步去哪里查詢。比如當(張三,F)這樣的數據來檢索時,b+樹可以用name來指定搜索方向,但下一個字段age的缺失,所以只能把名字等於張三的數據都找到,然后再匹配性別是F的數據了, 這個是非常重要的性質,即索引的最左匹配特性。
通過以上對B+樹的介紹,我們知道B+樹只有在葉子節點上才會存儲真實數據,而非葉子節點上只會存儲關鍵字和指針。
主鍵索引
前面提到mysql中是通過B+樹來組織一張表數據的,而B+樹每個節點上都有一個關鍵字,在進行搜索的時候要從根節點開始查找,直到在葉子節點上查詢到對應的關鍵字和這行數據。那么MySQL中是使用什么作為B+樹節點上的關鍵字呢?答案是主鍵索引,MySQL是通過主鍵索引作為B+樹節點上的關鍵字來組織數據的。那么MySQL又是怎樣確定使用哪個字段作為主鍵索引呢?規則如下:
- 如果建表時指定了主鍵,則使用主鍵作為B+樹節點的關鍵字。
- 如果表中沒有主鍵,但是有唯一索引,則會選取一個唯一索引作為關鍵字。
- 如果既沒有主鍵也沒有唯一索引,MySQL會自動生成一個6字節的整型唯一標識作為關鍵字。
也就是說,MySQL每張表中都必須有一個主鍵索引,使用這個主鍵索引作為關鍵字將整張表組織成一棵B+樹。
二級索引
主鍵索引是MySQL自動創建的,不是由用戶創建的。而在使用中為了提高查詢效率,開發者會根據查詢條件自己創建一些索引,而這些索引就叫作二級索引。
聚簇索引和非聚簇索引
在MySQL中不管是InnoDB還是MyISAM都是使用B+樹來組織表中的數據的。但是在具體實現方法上略有不同。InnoDB的主鍵索引是聚簇索引,在InnoDB的實現中,把主鍵作為關鍵字組織到B+樹的各個節點上,而葉子節點上存儲的是主鍵列的值和對應的整行數據。注意這里說的是將表中實際的一整行數據直接存到葉子節點上MyISAM是非聚簇索引,在MyISAM的實現中,葉子節點中存儲的是一行數據在磁盤上的地址(可以理解為行號)。所以聚簇索引和非聚簇索引本質的區別就是B+樹的葉子節點上存儲的是行數據還是行數據的地址(行號)。
InnoDB中每張表有且僅有一個聚簇索引(就是主鍵索引),InnoDB中的二級索引是非聚簇索引,那二級索引是怎么組織的呢?
InnoDB和MyISAM主鍵索引和二級索引的對比:
從圖中可以看出,InnoDB中的二級索引的葉子結點中存的是索引列的值和主鍵值,所以在使用二級索引查詢的時候,首先通過二級索引查找到主鍵值,然后再根據主鍵值到主鍵索引的葉子結點中查到對應的整行數據。
而MyISAM的二級索引的葉子節點中保存的是指向物理數據的指針,因此它的主建索引和二級索引的結構並沒有任何區別,只是說主鍵索引的索引值是唯一且非空的。
InnoDB的主鍵索引設計成聚簇索引有兩個優點:
- 數據和主鍵索引存放在一起,可以直接根據主鍵值獲取到整行數據,如果是非聚簇索引則只能根據主鍵值獲取到行數據的地址,然后再讀一次磁盤去獲取行數據。
- 因為B+樹的葉子節點是根據關鍵字排序的,所以表中主鍵值連續的數據在磁盤中的存儲也是連續的。
回表查詢和索引覆蓋
有如下一張InnoDB表:
CREATE TABLE `user` ( `id` INT NOT NULL , `name` VARCHAR NOT NULL , `age` INT NOT NULL);
- 1
- 2
- 3
- 4
其中id為自增主鍵,name是一個普通索引。在執行select * from user where id = 1
時,會在主鍵索引對應的B+樹的葉子結點上搜索到關鍵字id=1的節點,並讀取位於該節點上的整行數據。但是在執行select * from user where name = 'tom'
時,會分為兩個步驟:
-
先到name索引對應的B+樹的葉子結點上搜索到關鍵字name='tom’的節點,並從該節點上獲取對應的主鍵id值。
-
然后再根據id值使用主鍵索引讀取到整行數據。
其中第二個步驟叫作回表查詢。需要掃描普通索引和主鍵索引兩棵B+樹才能拿到整行數據,效率較低。
如果執行select id, name from user where name = 'tom'
,則只需要掃描name索引樹就可以獲取到所有的字段,因為id和name都保存在name索引B+樹的葉子節點上,所以不需要再去主鍵索引上查找。這就是所謂的索引覆蓋。只需要在一棵索引樹上就能獲取SQL所需的所有列數據,無需回表,速度更快。
而select id, name, age from user where name = 'tom'
,因為age字段沒有存儲到name索引的葉子節點上,所以需要根據主鍵索引回表查詢到age列值。如果把name索引改成(name,age)的聯合索引就可以實現索引覆蓋,無需回表了。
轉載自:https://blog.csdn.net/qq_37584164/article/details/108908719