Mysql存储结构B树与B+树与索引
首先要說明的是,B-樹和B樹是指同一個結構,并沒有所謂的B減樹,兩種樹是B-樹和B+樹。
Mysql存儲結構是一個B+樹。
1.存儲結構與索引
眾所周知,索引是關系型數據庫中給數據庫表中一列或多列的值排序后的存儲結構,它是一種加快查詢速度的數據結構,常用索引結構有hash、B-Tree和B+Tree,Mysql選用的是B+樹索引。
1)Hash
hash是基于哈希表完成索引存儲,哈希表特性是數據存放是散列的。
優點:
等值查詢快,通過hash值直接定位到具體的數據。
缺點:
2)B-Tree
a)
B樹(或B-樹、B_樹),它是一種m階平衡多叉樹。當m取2時,便是二叉搜索樹,其中m指的是一個結點最多有多少個孩子結點。
? ? 對于m階B樹,其具有如下性質:
如下圖所示:
上圖為3階B樹,在實際應用中的B樹的階數m都非常大(通常大于100),所以即使存儲大量的數據,B樹的高度仍然比較小,這有利于樹的插入刪除。在數據庫中我們將B樹(和B+樹)作為索引結構,可以加快查詢速速。
? B樹的插入
?如果插入的結點只有一個數值,直接在該結點插入即可。例如,在上圖中插入9,則直接在10結點前面插入9即可。但如果插入44,此時便需要通過結點的向上分裂來完成插入。
? ?? 插入44:
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ???
? ? 發現此結點有3個值,不滿足3階B樹,因此要進行分裂,將中間的40向上結點移動:
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ???
? ? 分裂后此B樹變成了4階B樹,不滿足3階B樹條件,原因是40移動到上結點所致,因此繼續向上結點移動,將50移動到上節點:
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??
? ?? 此時發現又出現3個值的結點,繼續進行分裂:
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??
? ? ? 此時便滿足條件,完成。
B樹的刪除按照插入的方法反過來操作即可,即父結點(如果不符合父結點大于左結點小于右結點的條件,則與上層父節點位置調換,直到符合條件為止)不斷下移合并,直到符合條件為止。
b)
B樹在磁盤存儲中:
B-Tree特點:
3)B+Tree
a)
在B+樹中,只有葉子節點存儲數據,其它中間結點全部是索引。在數據庫的聚集索引中,葉子節點直接包含數據庫中某一行數據。在非聚集索引中,葉子節點帶有指向數據庫行的指針。
B+樹是B樹的一種變體,有著比B樹更高的查詢性能,B+樹和B樹除了有一些共同特點外,還有一些新的特點:
?? 下面使用數值來表示一棵B+樹:
可以看出,B+樹的每個結點的最大或最小元素都出現在下一個結點的首或尾
B+樹的查找
? ?? B+樹的查找有兩種方式:從最小值進行順序查找;從根結點開始,進行隨機查找。在查找時,若非終端結點上的關鍵值等于給定值,并不終止,而是繼續向下直到葉子結點(因為葉子結點才存數據)。因此,在B+樹中,不管查找成功與否,每次查找都是走了一條從根到葉子結點的路徑。其余同B-樹的查找類似。
? ?? 由于B+樹的數據都存儲在葉子結點中,分支結點均為索引,方便掃庫,只需要掃一遍葉子結點即可,但是B樹因為其分支結點同樣存儲著數據,我們要找到具體的數據,需要進行一次中序遍歷按序來掃,所以B+樹更加適合在區間查詢的情況,所以通常B+樹用于數據庫索引,而B樹則常用于文件索引。
B+樹的插入
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??
假設我們要向上圖插入0,發現沒有破壞B+樹結構,直接在1,2結點處插入即可。
如果在結點的中間插入并破壞了B+樹的結構:
但是如果我們要插入12,則發現破壞了B+樹的結構,則:
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??
分裂破壞了結構的結點,并將12移到上結點:
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ???
插入完畢。
如果在端點處插入并破壞了B+樹的結構:
假如插入16:
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ???
分裂后,父結點要配合子結點的端點值:
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??
刪除操作,只需將插入操作進行反向操作即可。讀者可以想想如何刪除16。
B+數的優勢:
b)
Mysql存儲中
B+Tree 是在B-Tree的基礎之上做的一種優化,變化如下:
Mysql為什么選擇B+Tree
Mysql官網文檔中寫到InnoDB索引用的是 B-tree,但是底層用的是B+Tree。Mysql存儲數據是以頁為單位,默認一個頁可以存放16K數據。假設B-Tree和B+Tree都是3層深度,表中每個記錄為1K(假設的,一般不會這么大,別較真),那么三層深度的B-Tree存儲 16 x 16 x 16 = 4096(比這個數還要少,因為每個頁中還要存放指針和其它的數據)。B+Tree第一、二層存放的是key,假設是Long類型的主鍵,那么第一、二層每頁存放數據約為 16 x 1024 / 8 = 2048,三層深度可以存放 2048 x 2048 x 16 = 6700W。MySQL查詢過程是按頁加載數據的,每加載一頁就是一次IO操作,B+Tree進行三次IO可以查詢6700W數據量。從這里也可以知道Mysql一般設置三層深度就足夠了。
2.聚集索引與非聚集索引
聚集(clustered)索引,也叫聚簇索引
定義:數據行的物理順序與列值(一般是主鍵的那一列)的邏輯順序相同,一個表中只能擁有一個聚集索引。
打個比方,把數據表比作新華字典,聚集索引就是拼音目錄,而每個字存放的頁碼就是實際的數據物理地址,如果要查詢一個“草”字,我們只需要查詢“草”字對應在新華字典拼音目錄對應的頁碼,就可以查詢到對應的“草”字所在的位置,而拼音目錄對應的A-Z的字順序,和新華字典實際存儲的字的順序A-Z也是一樣的,如果我們中文新出了一個字,拼音開頭第一個是B,那么他插入的時候也要按照拼音目錄順序插入到A字的后面。
如下表所示:
第一列的地址表示該行數據在磁盤中的物理地址,后面三列表示數據庫表的真實數據,其中id是主鍵,建立了聚集索引
數據行的物理順序與列值的順序相同,如果我們查詢id比較靠后的數據,那么這行數據的地址在磁盤中的物理地址也會比較靠后。而且由于物理排列方式與聚集索引的順序相同,所以只能建立一個聚集索引。
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 聚集索引實際存放的示意圖
從上圖可以看出聚集索引的好處了,索引的葉子節點就是對應的數據節點(MySQL的MyISAM除外,此存儲引擎的聚集索引和非聚集索引只多了個唯一約束,其他沒什么區別),可以直接獲取到對應的全部列的數據,而非聚集索引在索引沒有覆蓋到對應的列的時候需要進行二次查詢。因此在查詢方面,聚集索引的速度往往會更占優勢
非聚集索引
定義:該索引中索引的邏輯順序與磁盤上行的物理存儲順序不同,一個表中可以擁有多個非聚集索引。
按照上述比喻,非聚集索引就像新華字典的偏旁字典,存放的結構順序與實際存放順序不一定一致。
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 非聚集索引實際存放的示意圖
非聚集索引的二次查詢問題
非聚集索引葉節點仍然是索引節點,只是有一個指針指向對應的數據塊,此如果使用非聚集索引查詢,而查詢列中包含了其他該索引沒有覆蓋的列,那么他還要進行第二次的查詢,查詢節點上對應的數據行的數據。
如有以下表t1
以及聚集索引clustered index(id), 非聚集索引index(username)。
使用以下語句進行查詢,不需要進行二次查詢,直接就可以從非聚集索引的節點里面就可以獲取到查詢列的數據。
select id, username from t1 where username = '小明' select username from t1 where username = '小明'但是使用以下語句進行查詢,就需要二次的查詢去獲取原數據行的score:
select username, score from t1 where username = '小明'如何解決非聚集索引的二次查詢(回表查詢)問題
復合索引(覆蓋索引):將被查詢的字段,建立到聯合索引里去。
建立兩列以上的索引,即可查詢復合索引里的列的數據而不需要進行回表二次查詢,如index(col1, col2),執行下面的語句
select col1, col2 from t1 where col1 = '213';要注意使用復合索引需要滿足最左側索引的原則,也就是查詢的時候如果where條件里面沒有最左邊的一到多列,索引就不會起作用。
總結:
下面舉一個具體的例子
?
InnoDB聚集索引和普通索引有什么差異?
InnoDB聚集索引的葉子節點存儲行記錄,因此, InnoDB必須要有,且只有一個聚集索引:
(1)如果表定義了PK,則PK就是聚集索引;
(2)如果表沒有定義PK,則第一個not NULL unique列是聚集索引;
(3)否則,InnoDB會創建一個隱藏的row-id作為聚集索引;
畫外音:所以PK查詢非???#xff0c;直接定位行記錄。
InnoDB普通索引的葉子節點存儲主鍵值。
畫外音:注意,不是存儲行記錄頭指針,MyISAM的索引葉子節點存儲記錄指針。
舉個栗子,不妨設有表:
t(id PK, name KEY, sex, flag);
畫外音:id是聚集索引,name是普通索引。
?
表中有四條記錄:
1, shenjian, m, A
3, zhangsan, m, A
5, lisi, m, A
9, wangwu, f, B
兩個B+樹索引分別如上圖:
(1)id為PK,聚集索引,葉子節點存儲行記錄;
(2)name為KEY,普通索引,葉子節點存儲PK值,即id;
?
既然從普通索引無法直接定位行記錄,那普通索引的查詢過程是怎么樣的呢?
通常情況下,需要掃碼兩遍索引樹。
?
例如:
| 1 | select?*?from?t?where?name='lisi'; |
是如何執行的呢?
如粉紅色路徑,需要掃碼兩遍索引樹:
(1)先通過普通索引定位到主鍵值id=5;
(2)在通過聚集索引定位到行記錄;
?
這就是所謂的回表查詢,先定位主鍵值,再定位行記錄,它的性能較掃一遍索引樹更低。
提高效率,索引覆蓋!如何實現索引覆蓋:常見的方法是:將被查詢的字段,建立到聯合索引里去。
仍是之前中的例子:
| 1 2 3 4 5 6 | create?table?user?( ????id?int?primary?key, ????name?varchar(20), ????sex?varchar(5), ????index(name) )engine=innodb; |
1. 第一個sql?
| 1 | select?id,name?from?user?where?name='shenjian'; |
能夠命中name索引,索引葉子節點存儲了主鍵id,通過name的索引樹即可獲取id和name,無需回表,符合索引覆蓋,效率較高。
2. 第二個sql?
| 1 | select?id,name,sex?from?user?where?name='shenjian'; |
能夠命中name索引,索引葉子節點存儲了主鍵id,但sex字段必須回表查詢才能獲取到,不符合索引覆蓋,需要再次通過id值掃碼聚集索引獲取sex字段,效率會降低。
如果把(name)單列索引升級為聯合索引(name, sex)就不同了。
| 1 2 3 4 5 6 | create?table?user?( ????id?int?primary?key, ????name?varchar(20), ????sex?varchar(5), ????index(name, sex) )engine=innodb; |
可以看到:
| 1 2 3 | select?id,name?...?where?name='shenjian'; ? select?id,name,sex ...?where?name='shenjian'; |
都能夠命中索引覆蓋,無需回表。
?
索引覆蓋優化SQL場景
場景1:全表count查詢優化?
關于這點的解釋,可以參考這里,count(*)的查詢:
其原因是:在innodb中,非主鍵索引葉子節點存儲的結構是:索引+主鍵;主鍵索引葉子節點是:主鍵+表數據。在1個page里面,非主鍵索引可以存儲更多的條目,例如:
對于一張表,如果有1000000數據,使用非主鍵索引掃描的page數可能是100 ,而使用主鍵索引page數可能是500,此時使用非主鍵索引的性能會更好。同理如果存在多個非主鍵索引,會使用一個最小的非主鍵索引,也是為了在一個page里存儲更多的數據,從而減少掃描次數,提高性能。
這里使用的是單字段count()查詢,意思差不多。
原表為:
user(PK id, name, sex);
直接:
| 1 | select?count(name)?from?user; |
不能利用索引覆蓋。
添加索引:
| 1 | alter?table?user?add?key(name); |
就能夠利用索引覆蓋提效。
場景2:列查詢回表優化
| 1 | select?id,name,sex ...?where?name='shenjian'; |
這個例子不再贅述,將單列索引(name)升級為聯合索引(name, sex),即可避免回表。
場景3:分頁查詢
| 1 | select?id,name,sex ...?order?by?name?limit 500,100; |
將單列索引(name)升級為聯合索引(name, sex),也可以避免回表。
最后這一段
總結
以上是生活随笔為你收集整理的Mysql存储结构B树与B+树与索引的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: dosbox运行C语言,DOSBox怎么
- 下一篇: 在循环里创建数据库连接,严重影响数据库性