第18期:索引設計(認識哈希表)

image

MySQL 的默認索引結構是 B+ 樹,也能夠指定索引結構爲 HASH 或者 R 樹等其餘結構來適應不一樣的檢索需求。這裏咱們來介紹 MySQL 哈希索引。mysql

MySQL 哈希索引又基於哈希表(散列表)來實現,因此瞭解什麼是哈希表對 MySQL 哈希索引的理解相當重要。接下來,咱們來一步一部介紹哈希表。redis

1. 數組

數組是最經常使用的數據結構,是一種線性表的順序存儲方式,由下標(也叫索引)和對應的值構成。數組在各個開發語言以及數據庫中都有相似的結構,相似下圖1:sql

array1

圖 1 展現了一個一維整數數組,數組的長度爲 10,下標從 0-9, 每一個下標對應不一樣的值。每種編程語言基本上都有數組,大部分數據庫也提供了數組或者是相似數組的結構,MySQL 也有數組,如下爲 MySQL 的一維數組:mongodb

mysql> select @a as "array",json_length(@a) as "array_size";
+-------------------------------------------+------------+
| array                                     | array_size |
+-------------------------------------------+------------+
| [10, 20, 30, 40, 50, 60, 70, 80, 90, 100] |         10 |
+-------------------------------------------+------------+
1 row in set (0.00 sec)

數組元素也能夠是數組,這樣的表示稱爲多維數組,如圖 2,一個二維字符串數組:數據庫

array2

如下爲 MySQL 裏的多維數組:編程

mysql> select json_pretty(@a)\G
*************************** 1. row ***************************
json_pretty(@a): [
  [
    "mysql",
    "db2"
  ],
  [
    "oracle",
    "mongodb",
    "sql server",
    "redis"
  ],
  [
    "memcached",
    "dble",
    "postgresql",
    "mariadb"
  ]
]
1 row in set (0.01 sec)

數組優缺點以下,

優勢:json

數組最大的優勢是能夠根據下標來快速讀取到對應的值,通俗的說法就是時間複雜度爲 O(1)。數組

缺點:數據結構

1)對數組的寫入(插入或者刪除)要涉及到原下標對應值的遷移以及新下標的生成;oracle

2) 數組存儲須要一塊連續的存儲區域,後期數組擴容須要申請新的連續存儲區域,形成空間浪費。

2. 字典

字典和數組結構相似,不一樣的是,下標並不是是從 0 開始的數字,而是任意的字符串。有的程序語言裏把字典也叫數組,由 Key 映射爲對應的 value,字典的結構相似於圖 2:

dic1

MySQL 也一樣提供了這樣的字典,好比下面定義了一個字典,存入變量 @a,把圖 2 裏前 4 個元素拿出來,對應的 value 分別爲 「mysql","db2","oracle","mongodb".

mysql> set @a='{"10":"mysql","20":"db2","30":"oracle","40":"mongodb"}';
Query OK, 0 rows affected (0.00 sec)

mysql> select json_keys(@a);
+--------------------------+
| json_keys(@a)            |
+--------------------------+
| ["10", "20", "30", "40"] |
+--------------------------+
1 row in set (0.00 sec)

mysql> set @x1=json_extract(@a,'$."10"');
Query OK, 0 rows affected (0.01 sec)

mysql> set @x2=json_extract(@a,'$."20"');
Query OK, 0 rows affected (0.00 sec)

mysql> set @x3=json_extract(@a,'$."30"');
Query OK, 0 rows affected (0.00 sec)

mysql> set @x4=json_extract(@a,'$."40"');
Query OK, 0 rows affected (0.00 sec)

mysql> select @x1 "dict['10']", @x2 "dict['20']", @x3 "dict['30']", @x4 "dict['40']";
+------------+------------+------------+------------+
| dict['10'] | dict['20'] | dict['30'] | dict['40'] |
+------------+------------+------------+------------+
| "mysql"    | "db2"      | "oracle"   | "mongodb"  |
+------------+------------+------------+------------+
1 row in set (0.00 sec)

3. 鏈表

鏈表也是一種線性表的存儲結構,可是和數組不同,存儲線性表數據的單元並不是順序的。每一個元素(也叫節點)包含了本身的值以及指向下一個元素地址的指針。

好比圖 3,一個單線鏈表,MySQL 的 B+ 樹索引葉子節點就是一個鏈表結構。

鏈表1

鏈表的優缺點以下,

優勢:

1) 鏈表不須要連續的存儲區域,任何空餘的存儲區域均可以保存鏈表元素,只要指針指向正確的地址便可。

2) 對鏈表的更改(插入或者刪除)操做很是快,時間複雜度爲 O(1),只須要更改節點對應的指針便可,不須要挪動任何數據。好比上圖,往 「MySQL」 和 「DB2」 中間插入一個新的元素 「maxdb」,只須要把 「MySQL" 的指針指向 「maxdb",同時把 "maxdb" 的指針指向 "db2" 便可。

缺點:

沒法快速定位到指定的元素,必須從鏈表開頭的第一個元素順序查找,假設要查找的元素是鏈表的最後一個,那須要把每一個元素都掃描一遍,時間複雜度爲 O(N) 。

4. 哈希表(散列表)

哈希表(散列表),表現爲根據 {key,value}(相似字典)直接訪問的一種數據結構。哈希表通常用數組來保存,其中下標是根據一個固定的函數 func1(散列函數)帶入參數 key 計算的結果,value 爲對應的數據。對於數組 a 來講,a[func1(key)] = value。好比圖 4,func1 這裏爲取模函數 mod(key,9):

hash_table1

從上圖能夠發現如下幾個問題:

1)數組的值直接保存了對應的 VALUE,好比相同下標對應多個 VALUE,每一個 VALUE 自己又佔用很大空間,那查詢這樣的 VALUE 時,就得在內存中申請一塊連續的存儲區域。

2)數組的寫入效率不好,VALUE 存在數據的值裏是否合適?

3)  數組的下標生成有重複,也就是說散列函數的結果不惟一,也叫散列值發生碰撞。

那如何規避掉以上問題? 答案是確定的!

針對前兩個問題,能夠把數組和鏈表結合起來,這樣既可使用數組的高性能隨機讀,又能使用鏈表的高性能隨機寫,這種通常叫作拉鍊法,見圖 5:

hash_table2

圖 5 所示的散列表依然用數組保存,下標爲散列函數根據 KEY 計算的結果,值變爲指向一個鏈表的指針,鏈表裏保存了對應的 VALUE,這樣的優勢是數組自己佔用空間不大,後期須要擴容效率也高。

好比查找 key 爲 20 對應的 VALUE,經過函數 func1 計算獲得結果爲 2,就能夠很快找到下標爲 2 的值。

那接下來看圖 4 裏發現的最後一個問題,散列函數的選擇。理論上來說,對任何鍵值都有可能存在一個完美的散列函數而且不會發生任何碰撞,可是現實場景中找一個散列碰撞極少的散列函數就已經很優化了。

大體有兩個層面要考慮,

1) 數據分佈

好比上面的取模函數,針對整數類型集合,若是除數足夠大,其生成結果產生的碰撞概率就足夠小。舉個簡單例子,

如下 Key 集合 {1,2,...,1000000},有 100W 個元素,每一個元素類型都爲無符號整數,那這樣,能夠用最大值 1000000 來作基數取模,每一個值的散列結果都惟一。可是這個得提早獲知集合的大小以及類型。

2) 散列函數的效率

散列表能快速查找,歸功於散列函數的快速計算,若是一個散列函數計算耗時好久,那對應的散列表查找也就不可能很快。通常來講,散列函數的複雜度都假設爲趨近於 O(1),一個好的散列函數理論上應該是穩定、快速。好比 MySQL 的哈希分區就用的函數 password。下圖 6 是基於一個很是差的散列函數生成的散列表。能夠看到結果屢次碰撞,應該避免這種場景發生。

hash_table3

對上圖中的散列表來講,不可能快速檢索。不過能夠考慮當鏈表到達必定的長度後,把鏈表變爲一棵 AVL 樹來加快檢索效率。散列表的實現除了通常的拉鍊法還有好比開放地址法等,感興趣的能夠深刻研究。

哈希表(散列表)的優缺點總結以下,

優勢:

哈希表的目的是讓寫入和查找時間複雜度都接近常量 O(1),因此小表作哈希性能很是好。

缺點:

要提早預判用來生成哈希表的基礎表數據量,防止數據量過大,哈希表被撐大。

要找到合適的哈希函數,以防哈希表碰撞太頻繁。

總結

哈希索引的實現就是創建在散列表的基礎上,把索引字段當成 KEY,經過散列函數計算結果後,指向對應的行記錄。認識哈希表對後期的 INNODB 自適應哈希索引以及對 HASH JOIN 的理解就會更加深入。


關於 MySQL 的技術內容,大家還有什麼想知道的嗎?趕忙留言告訴小編吧!

image

相關文章
相關標籤/搜索