日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > 数据库 >内容正文

数据库

Mysql存储结构B树与B+树与索引

發(fā)布時間:2023/12/10 数据库 42 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Mysql存储结构B树与B+树与索引 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

首先要說明的是,B-樹和B樹是指同一個結(jié)構(gòu),并沒有所謂的B減樹,兩種樹是B-樹和B+樹。

Mysql存儲結(jié)構(gòu)是一個B+樹。

1.存儲結(jié)構(gòu)與索引

眾所周知,索引是關(guān)系型數(shù)據(jù)庫中給數(shù)據(jù)庫表中一列或多列的值排序后的存儲結(jié)構(gòu),它是一種加快查詢速度的數(shù)據(jù)結(jié)構(gòu),常用索引結(jié)構(gòu)有hash、B-Tree和B+Tree,Mysql選用的是B+樹索引。

1)Hash

hash是基于哈希表完成索引存儲,哈希表特性是數(shù)據(jù)存放是散列的。

優(yōu)點:

等值查詢快,通過hash值直接定位到具體的數(shù)據(jù)。

缺點:

  • 范圍查詢效率低(表中的數(shù)據(jù)是無序數(shù)據(jù),在日常開發(fā)中通常需要范圍查詢,該情況下hash需要一個一個查找后合并返回)
  • hash表在使用的時會將所有數(shù)據(jù)加載到內(nèi)存,比較消耗內(nèi)存
  • hash算法不好會出現(xiàn)hash碰撞的情況
  • 哈希索引只包含哈希值和行指針,而不存儲字段值,索引不能使用索引中的值來避免讀取行
  • 哈希索引不支持部分列匹配查找,哈希索引是使用索引列的全部內(nèi)容來計算哈希值
  • 2)B-Tree

    a)

    B樹(或B-樹、B_樹),它是一種m階平衡多叉樹。當m取2時,便是二叉搜索樹,其中m指的是一個結(jié)點最多有多少個孩子結(jié)點。

    ? ? 對于m階B樹,其具有如下性質(zhì):

  • 根結(jié)點至少有兩個子女;
  • 每個結(jié)點的值的個數(shù)為 1 <= n < m;
  • 所有的葉子結(jié)點都位于同一層;
  • 除根結(jié)點以外的所有結(jié)點(不包括葉子結(jié)點)的孩子正好是值個數(shù)的加1;
  • 每個結(jié)點中的值都按照從小到大的順序排列,每個值的左子樹中的所有的值都小于它,而右子樹中的所有的值都大于它。
  • 如下圖所示:

    上圖為3階B樹,在實際應用中的B樹的階數(shù)m都非常大(通常大于100),所以即使存儲大量的數(shù)據(jù),B樹的高度仍然比較小,這有利于樹的插入刪除。在數(shù)據(jù)庫中我們將B樹(和B+樹)作為索引結(jié)構(gòu),可以加快查詢速速。

    ? B樹的插入

    ?如果插入的結(jié)點只有一個數(shù)值,直接在該結(jié)點插入即可。例如,在上圖中插入9,則直接在10結(jié)點前面插入9即可。但如果插入44,此時便需要通過結(jié)點的向上分裂來完成插入。

    ? ?? 插入44:

    ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ???

    ? ? 發(fā)現(xiàn)此結(jié)點有3個值,不滿足3階B樹,因此要進行分裂,將中間的40向上結(jié)點移動:

    ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ???

    ? ? 分裂后此B樹變成了4階B樹,不滿足3階B樹條件,原因是40移動到上結(jié)點所致,因此繼續(xù)向上結(jié)點移動,將50移動到上節(jié)點:

    ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??

    ? ?? 此時發(fā)現(xiàn)又出現(xiàn)3個值的結(jié)點,繼續(xù)進行分裂:

    ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??

    ? ? ? 此時便滿足條件,完成。

    B樹的刪除按照插入的方法反過來操作即可,即父結(jié)點(如果不符合父結(jié)點大于左結(jié)點小于右結(jié)點的條件,則與上層父節(jié)點位置調(diào)換,直到符合條件為止)不斷下移合并,直到符合條件為止。

    b)

    B樹在磁盤存儲中:

    B-Tree特點:

  • 所有鍵值數(shù)據(jù)分布在整棵樹各個節(jié)點中
  • 搜索有可能在非節(jié)點結(jié)束,在關(guān)鍵字全集內(nèi)查找,類似二分查找
  • 所有葉子節(jié)點都在同一層,并且以升序排列
  • 3)B+Tree

    a)

    在B+樹中,只有葉子節(jié)點存儲數(shù)據(jù),其它中間結(jié)點全部是索引。在數(shù)據(jù)庫的聚集索引中,葉子節(jié)點直接包含數(shù)據(jù)庫中某一行數(shù)據(jù)。在非聚集索引中,葉子節(jié)點帶有指向數(shù)據(jù)庫行的指針。

    B+樹是B樹的一種變體,有著比B樹更高的查詢性能,B+樹和B樹除了有一些共同特點外,還有一些新的特點:

  • 有k個子樹的中間結(jié)點包含有k個元素(B樹中是k-1個元素),每個元素不保存數(shù)據(jù),只用來索引,所有數(shù)據(jù)都保存在葉子節(jié)點。
  • 所有的葉子結(jié)點中包含了全部元素的信息,及指向含這些元素記錄的指針,且葉子結(jié)點本身依關(guān)鍵字的大小自小而大順序鏈接。
  • 所有的中間結(jié)點元素都同時存在于子節(jié)點,在子節(jié)點元素中是最大(或最小)元素。
  • ?? 下面使用數(shù)值來表示一棵B+樹:

    可以看出,B+樹的每個結(jié)點的最大或最小元素都出現(xiàn)在下一個結(jié)點的首或尾

    B+樹的查找

    ? ?? B+樹的查找有兩種方式:從最小值進行順序查找;從根結(jié)點開始,進行隨機查找。在查找時,若非終端結(jié)點上的關(guān)鍵值等于給定值,并不終止,而是繼續(xù)向下直到葉子結(jié)點(因為葉子結(jié)點才存數(shù)據(jù))。因此,在B+樹中,不管查找成功與否,每次查找都是走了一條從根到葉子結(jié)點的路徑。其余同B-樹的查找類似。

    ? ?? 由于B+樹的數(shù)據(jù)都存儲在葉子結(jié)點中,分支結(jié)點均為索引,方便掃庫,只需要掃一遍葉子結(jié)點即可,但是B樹因為其分支結(jié)點同樣存儲著數(shù)據(jù),我們要找到具體的數(shù)據(jù),需要進行一次中序遍歷按序來掃,所以B+樹更加適合在區(qū)間查詢的情況,所以通常B+樹用于數(shù)據(jù)庫索引,而B樹則常用于文件索引。

    B+樹的插入

    ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??

    假設我們要向上圖插入0,發(fā)現(xiàn)沒有破壞B+樹結(jié)構(gòu),直接在1,2結(jié)點處插入即可。

    如果在結(jié)點的中間插入并破壞了B+樹的結(jié)構(gòu):

    但是如果我們要插入12,則發(fā)現(xiàn)破壞了B+樹的結(jié)構(gòu),則:

    ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??

    分裂破壞了結(jié)構(gòu)的結(jié)點,并將12移到上結(jié)點:

    ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ???

    插入完畢。

    如果在端點處插入并破壞了B+樹的結(jié)構(gòu):

    假如插入16:

    ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ???

    分裂后,父結(jié)點要配合子結(jié)點的端點值:

    ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??

    刪除操作,只需將插入操作進行反向操作即可。讀者可以想想如何刪除16。

    B+數(shù)的優(yōu)勢:

  • 單一節(jié)點存儲更多的元素,使得查詢的IO次數(shù)更少。(應用于文件系統(tǒng)、數(shù)據(jù)庫系統(tǒng))
  • 所有查詢都要查找到葉子節(jié)點,查詢性能穩(wěn)定。
  • 所有葉子節(jié)點形成有序鏈表,便于范圍查詢。
  • b)

    Mysql存儲中

    B+Tree 是在B-Tree的基礎(chǔ)之上做的一種優(yōu)化,變化如下:

  • B+Tree 非葉子節(jié)點不存放數(shù)據(jù)
  • 葉子節(jié)點存儲關(guān)鍵字和數(shù)據(jù),非葉子節(jié)點的關(guān)鍵字也會沉到葉子節(jié)點,并且排序
  • 葉子節(jié)點兩兩指針相互連接,形成一個雙向環(huán)形鏈表(符合磁盤的預讀特性),順序查詢性能更高
  • Mysql為什么選擇B+Tree

    Mysql官網(wǎng)文檔中寫到InnoDB索引用的是 B-tree,但是底層用的是B+Tree。Mysql存儲數(shù)據(jù)是以頁為單位,默認一個頁可以存放16K數(shù)據(jù)。假設B-Tree和B+Tree都是3層深度,表中每個記錄為1K(假設的,一般不會這么大,別較真),那么三層深度的B-Tree存儲 16 x 16 x 16 = 4096(比這個數(shù)還要少,因為每個頁中還要存放指針和其它的數(shù)據(jù))。B+Tree第一、二層存放的是key,假設是Long類型的主鍵,那么第一、二層每頁存放數(shù)據(jù)約為 16 x 1024 / 8 = 2048,三層深度可以存放 2048 x 2048 x 16 = 6700W。MySQL查詢過程是按頁加載數(shù)據(jù)的,每加載一頁就是一次IO操作,B+Tree進行三次IO可以查詢6700W數(shù)據(jù)量。從這里也可以知道Mysql一般設置三層深度就足夠了。

    2.聚集索引與非聚集索引

    聚集(clustered)索引,也叫聚簇索引

    定義:數(shù)據(jù)行的物理順序與列值(一般是主鍵的那一列)的邏輯順序相同,一個表中只能擁有一個聚集索引。

    打個比方,把數(shù)據(jù)表比作新華字典,聚集索引就是拼音目錄,而每個字存放的頁碼就是實際的數(shù)據(jù)物理地址,如果要查詢一個“草”字,我們只需要查詢“草”字對應在新華字典拼音目錄對應的頁碼,就可以查詢到對應的“草”字所在的位置,而拼音目錄對應的A-Z的字順序,和新華字典實際存儲的字的順序A-Z也是一樣的,如果我們中文新出了一個字,拼音開頭第一個是B,那么他插入的時候也要按照拼音目錄順序插入到A字的后面。

    如下表所示:

    第一列的地址表示該行數(shù)據(jù)在磁盤中的物理地址,后面三列表示數(shù)據(jù)庫表的真實數(shù)據(jù),其中id是主鍵,建立了聚集索引

    數(shù)據(jù)行的物理順序與列值的順序相同,如果我們查詢id比較靠后的數(shù)據(jù),那么這行數(shù)據(jù)的地址在磁盤中的物理地址也會比較靠后。而且由于物理排列方式與聚集索引的順序相同,所以只能建立一個聚集索引。

    ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 聚集索引實際存放的示意圖

    從上圖可以看出聚集索引的好處了,索引的葉子節(jié)點就是對應的數(shù)據(jù)節(jié)點(MySQL的MyISAM除外,此存儲引擎的聚集索引和非聚集索引只多了個唯一約束,其他沒什么區(qū)別),可以直接獲取到對應的全部列的數(shù)據(jù),而非聚集索引在索引沒有覆蓋到對應的列的時候需要進行二次查詢。因此在查詢方面,聚集索引的速度往往會更占優(yōu)勢

    非聚集索引

    定義:該索引中索引的邏輯順序與磁盤上行的物理存儲順序不同,一個表中可以擁有多個非聚集索引。

    按照上述比喻,非聚集索引就像新華字典的偏旁字典,存放的結(jié)構(gòu)順序與實際存放順序不一定一致。

    ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 非聚集索引實際存放的示意圖

    非聚集索引的二次查詢問題

    非聚集索引葉節(jié)點仍然是索引節(jié)點,只是有一個指針指向?qū)臄?shù)據(jù)塊,此如果使用非聚集索引查詢,而查詢列中包含了其他該索引沒有覆蓋的列,那么他還要進行第二次的查詢,查詢節(jié)點上對應的數(shù)據(jù)行的數(shù)據(jù)。

    如有以下表t1

    以及聚集索引clustered index(id), 非聚集索引index(username)。

    使用以下語句進行查詢,不需要進行二次查詢,直接就可以從非聚集索引的節(jié)點里面就可以獲取到查詢列的數(shù)據(jù)。

    select id, username from t1 where username = '小明' select username from t1 where username = '小明'

    但是使用以下語句進行查詢,就需要二次的查詢?nèi)カ@取原數(shù)據(jù)行的score:

    select username, score from t1 where username = '小明'

    如何解決非聚集索引的二次查詢(回表查詢)問題

    復合索引(覆蓋索引):將被查詢的字段,建立到聯(lián)合索引里去。

    建立兩列以上的索引,即可查詢復合索引里的列的數(shù)據(jù)而不需要進行回表二次查詢,如index(col1, col2),執(zhí)行下面的語句

    select col1, col2 from t1 where col1 = '213';

    要注意使用復合索引需要滿足最左側(cè)索引的原則,也就是查詢的時候如果where條件里面沒有最左邊的一到多列,索引就不會起作用。

    總結(jié):

  • 使用聚集索引的查詢效率要比非聚集索引的效率要高,但是如果需要頻繁去改變聚集索引的值,寫入性能并不高,因為需要移動對應數(shù)據(jù)的物理位置。
  • 非聚集索引在查詢的時候可以的話就避免二次查詢,這樣性能會大幅提升。
  • 不是所有的表都適合建立索引,只有數(shù)據(jù)量大表才適合建立索引,且建立在選擇性高的列上面性能會更好。
  • 下面舉一個具體的例子

    ?

    InnoDB聚集索引和普通索引有什么差異?

    InnoDB聚集索引的葉子節(jié)點存儲行記錄,因此, InnoDB必須要有,且只有一個聚集索引:

    (1)如果表定義了PK,則PK就是聚集索引;

    (2)如果表沒有定義PK,則第一個not NULL unique列是聚集索引;

    (3)否則,InnoDB會創(chuàng)建一個隱藏的row-id作為聚集索引;

    畫外音:所以PK查詢非常快,直接定位行記錄。

    InnoDB普通索引的葉子節(jié)點存儲主鍵值。

     畫外音:注意,不是存儲行記錄頭指針,MyISAM的索引葉子節(jié)點存儲記錄指針。

    舉個栗子,不妨設有表:

      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,聚集索引,葉子節(jié)點存儲行記錄;

      (2)name為KEY,普通索引,葉子節(jié)點存儲PK值,即id;

    ?

    既然從普通索引無法直接定位行記錄,那普通索引的查詢過程是怎么樣的呢?

    通常情況下,需要掃碼兩遍索引樹。

    ?

    例如:

    1

    select?*?from?t?where?name='lisi'; 

    是如何執(zhí)行的呢?

    粉紅色路徑,需要掃碼兩遍索引樹:

    (1)先通過普通索引定位到主鍵值id=5;

    (2)在通過聚集索引定位到行記錄;

    ?

    這就是所謂的回表查詢,先定位主鍵值,再定位行記錄,它的性能較掃一遍索引樹更低。

    提高效率,索引覆蓋!如何實現(xiàn)索引覆蓋:常見的方法是:將被查詢的字段,建立到聯(lián)合索引里去。

    仍是之前中的例子:

    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索引,索引葉子節(jié)點存儲了主鍵id,通過name的索引樹即可獲取id和name,無需回表,符合索引覆蓋,效率較高。

    2. 第二個sql?

    1

    select?id,name,sex?from?user?where?name='shenjian';

    能夠命中name索引,索引葉子節(jié)點存儲了主鍵id,但sex字段必須回表查詢才能獲取到,不符合索引覆蓋,需要再次通過id值掃碼聚集索引獲取sex字段,效率會降低。

    如果把(name)單列索引升級為聯(lián)合索引(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';

    都能夠命中索引覆蓋,無需回表。

    ?

    索引覆蓋優(yōu)化SQL場景

    場景1:全表count查詢優(yōu)化?

    關(guān)于這點的解釋,可以參考這里,count(*)的查詢:

  • 當沒有非主鍵索引時,會使用主鍵索引
  • 如果存在非主鍵索引的話,會使用非主鍵索引
  • 如果存在多個非主鍵索引,會使用一個最小的非主鍵索引
  • 其原因是:在innodb中,非主鍵索引葉子節(jié)點存儲的結(jié)構(gòu)是:索引+主鍵;主鍵索引葉子節(jié)點是:主鍵+表數(shù)據(jù)。在1個page里面,非主鍵索引可以存儲更多的條目,例如:
    對于一張表,如果有1000000數(shù)據(jù),使用非主鍵索引掃描的page數(shù)可能是100 ,而使用主鍵索引page數(shù)可能是500,此時使用非主鍵索引的性能會更好。同理如果存在多個非主鍵索引,會使用一個最小的非主鍵索引,也是為了在一個page里存儲更多的數(shù)據(jù),從而減少掃描次數(shù),提高性能。

    這里使用的是單字段count()查詢,意思差不多。

    原表為:

    user(PK id, name, sex);

    直接:

    1

    select?count(name)?from?user;

    不能利用索引覆蓋。

    添加索引:

    1

    alter?table?user?add?key(name);

    就能夠利用索引覆蓋提效。

    場景2:列查詢回表優(yōu)化

    1

    select?id,name,sex ...?where?name='shenjian';

    這個例子不再贅述,將單列索引(name)升級為聯(lián)合索引(name, sex),即可避免回表。

    場景3:分頁查詢

    1

    select?id,name,sex ...?order?by?name?limit 500,100;

    將單列索引(name)升級為聯(lián)合索引(name, sex),也可以避免回表。

    最后這一段

    總結(jié)

    以上是生活随笔為你收集整理的Mysql存储结构B树与B+树与索引的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。