B-Tree與Hash索引

B-tree:(數據區+指針區+二分查找)

B-tree索引能夠加快訪問數據的速度,因爲存儲引擎不再需要經行全表掃描來獲取需要的數據,取而代之的是從根節點開始搜索。根節點的槽中存放了指向子節點的指針,存儲引擎根據這些指針向下查找。通常比較節點頁的值和要查找的值可以找到合適的指針進入下層子節點。

B-tree通常意味着所有的值都是按順序存儲的,並且每一個葉子頁到根的距離相同。

如上圖,是一顆B-tree,關於B-tree的定義可以參見B-tree,這裏只說一些重點,淺藍色的塊我們稱之爲一個磁盤塊,可以看到每個磁盤塊包含幾個數據項(深藍色所示)和指針(黃色所示),如磁盤塊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-tree的查找過程

  • 如圖所示,如果要查找數據項29,那麼首先會把磁盤塊1由磁盤加載到內存,此時發生一次IO,在內存中用二分查找確定29在17和35之間,鎖定磁盤塊1的P2指針,內存時間因爲非常短(相比磁盤的IO)可以忽略不計,通過磁盤塊1的P2指針的磁盤地址把磁盤塊3由磁盤加載到內存,發生第二次IO,29在26和30之間,鎖定磁盤塊3的P2指針,通過指針加載磁盤塊8到內存,發生第三次IO,同時內存中做二分查找找到29,結束查詢,總計三次IO。真實的情況是,3層的B-tree可以表示上百萬的數據,如果上百萬的數據查找只需要三次IO,性能提高將是巨大的,如果沒有索引,每個數據項都要發生一次IO,那麼總共需要百萬次的IO,顯然成本非常非常高。

二、 B-tree性質

  1. 通過上面的分析,我們知道IO次數取決於b+數的高度h,假設當前數據表的數據爲N,每個磁盤塊的數據項的數量是m,則有h=㏒(m+1)N,當數據量N一定的情況下,m越大,h越小;而m = 磁盤塊的大小 / 數據項的大小,磁盤塊的大小也就是一個數據頁的大小,是固定的,如果數據項佔的空間越小,數據項的數量越多,樹的高度越低。這就是爲什麼每個數據項,即索引字段要儘量的小,比如int佔4字節,要比bigint8字節少一半。這也是爲什麼B-tree要求把真實的數據放到葉子節點而不是內層節點,一旦放到內層節點,磁盤塊的數據項會大幅度下降,導致樹增高。當數據項等於1時將會退化成線性表。
  2. 當B-tree的數據項是複合的數據結構,比如(name,age,sex)的時候,b+數是按照從左到右的順序來建立搜索樹的,比如當(張三,20,F)這樣的數據來檢索的時候,B-tree會優先比較name來確定下一步的所搜方向,如果name相同再依次比較age和sex,最後得到檢索的數據;但當(20,F)這樣的沒有name的數據來的時候,B-tree就不知道下一步該查哪個節點,因爲建立搜索樹的時候name就是第一個比較因子,必須要先根據name來搜索才能知道下一步去哪裏查詢。比如當(張三,F)這樣的數據來檢索時,B-tree可以用name來指定搜索方向,但下一個字段age的缺失,所以只能把名字等於張三的數據都找到,然後再匹配性別是F的數據了, 這個是非常重要的性質,即索引的最左匹配特性

三、 B-tree索引建立原則

  1. 最左前綴匹配原則,非常重要的原則,mysql會一直向右匹配直到遇到範圍查詢(>、<、between、like)就停止匹配,比如a = 1 and b = 2 and c > 3and d = 4 如果建立(a,b,c,d)順序的索引,d是用不到索引的,如果建立(a,b,d,c)的索引則都可以用到,a,b,d的順序可以任意調整。
  2. = 和 in 可以亂序,比如a = 1 and b = 2and c = 3 建立(a,b,c)索引可以任意順序,mysql的查詢優化器會幫你優化成索引可以識別的形式
  3. 儘量選擇區分度高的列作爲索引,區分度的公式: count(distinct col)/count(*) ,表示字段不重複的比例,比例越大我們掃描的記錄數越少,唯一鍵的區分度是1,而一些狀態、性別字段可能在大數據面前區分度就是0,那可能有人會問,這個比例有什麼經驗值嗎?使用場景不同,這個值也很難確定,一般需要join的字段我們都要求是0.1以上,即平均1條掃描10條記錄
  4. 索引列不能參與計算,保持列“乾淨”,比如 from_unixtime(create_time) = ’2014-05-29’ 就不能使用到索引,原因很簡單,b+樹中存的都是數據表中的字段值,但進行檢索時,需要把所有元素都應用函數才能比較,顯然成本太大。所以語句應該寫成 create_time = unix_timestamp(’2014-05-29’);
  5. 儘量的擴展索引,不要新建索引。比如表中已經有a的索引,現在要加(a,b)的索引,那麼只需要修改原來的索引即可。

注意:

  • B-tree的高度一般都在2-4層,這也就是說查找某一鍵值的行記錄最多隻要2到4次IO,花費0.02-0.04秒左右。
  • B-tree索引適用於全值匹配、匹配最左前綴、匹配列前綴、匹配範圍值

Hash索引:(哈希碼+指針)

       哈希索引基於哈希表實現,只有精確匹配索引所有列的列纔有效。對於每一行數據,存儲引擎都會對所有索引計算一個哈希碼,哈希碼是一個較小的值並且不同鍵值計算出來的哈希碼都不一樣。哈希索引將所有的哈希碼存儲在索引中,同時在哈希表中保存指向每個數據的指針。

哈希索引限制:

  1. 哈希索引只包含哈希值和行指針,而不知存儲字段值,所以不能使用索引中的值來避免讀取行。若讀取行,則必須經行一次IO操作。
  2. 哈希索引並不是按照哈希值順序存儲的,所以也就無法用於排序。
  3. 哈希值也不支持部分索引,因爲哈希值始終是使用索引列中所有的內容計算哈希值的。例如,在數據(A,B)上建立哈希索引,如果只查詢A則不能使用哈希索引。(對比B-Tree索引中符合索引的最左匹配規則)
  4. 哈希索引只支持等值查詢。
  5. 哈希索引查詢速度非常快,除非出現出現衝突。

注意:

  • Mysql:只有在Memory引擎顯示支持哈希索引。(MyISAM、InnoDB、Memory)
  • Innodb引擎有一個特殊的功能叫做“自適應哈希索引”。當Innodb注意到某些索引值使用非常頻繁時,它會在內存中基於B-tree索引之上再建立一個哈希索引。這是一個完全自動、內部的行爲,用戶無法配置或者設置,不過有必要可以關閉此功能。

mysql常見索引:

普通索引:最基本的索引,沒有任何限制

  • 創建索引:CREATE INDEX indexName ON tableName(tableColumns(length));如果是CHAR,VARCHAR類型,length可以小於字段實際長度;如果是BLOB 和 TEXT 類型,必須指定length,下同。
  • 修改表結構:ALTER tableName ADD INDEX [indexName] ON (tableColumns(length)) 
  • 創建表的時候直接指定:CREATE TABLE tableName ( [...], INDEX [indexName] (tableColumns(length)) ;

唯一索引:與"普通索引"類似,不同的就是:索引列的值必須唯一,但允許有空值。

  • 創建索引:CREATE UNIQUE INDEX indexName ON tableName(tableColumns(length))
  • 修改表結構:ALTER tableName ADD UNIQUE [indexName] ON (tableColumns(length))
  • 創建表的時候直接指定:CREATE TABLE tableName ( [...], UNIQUE [indexName] (tableColumns(length));

全文索引:針對較大的數據,生成全文索引很耗時耗空間。(MyIsam和InnoDb支持:優秀博主)

  • ALTER TABLE `table_name` ADD FULLTEXT ( `column` )
  • create fulltext index ik_1 on table_1(col_1);
  • SELECT * FROM article WHERE MATCH(title,content) AGAINST (‘查詢字符串');

組合索引:爲了更多的提高mysql效率可建立組合索引,遵循”最左前綴“原則。

  • ALTER TABLE `table_name` ADD INDEX index_name ( `column1`, `column2`, `column3` )

主鍵索引:它是一種特殊的唯一索引,不允許有空值。

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