1. 程式人生 > >MySQL索引演算法——雜湊演算法

MySQL索引演算法——雜湊演算法

雜湊索引
雜湊索引(hash index)基於雜湊表實現,只有精確匹配索引所有列的查詢才有效。對於每一行資料,儲存引擎都會對所有的索引列計算一個雜湊碼(hash code),  雜湊碼是一個較小的值,並且不同鍵值的行計算出來的雜湊碼也不一樣。雜湊索引將所有的雜湊碼儲存在索引中,同時在雜湊表中儲存指向每個資料行的指標。

在MySQL中,只有Memory引擎顯式支援雜湊索引。這也是Memory引擎表的預設索引型別,Memory引擎同時也支援B-Tree索引。值得一提的是,Memory引擎是支援非唯一雜湊索引的,這在資料庫世界裡面是比較與眾不同的。如果多個列的雜湊值相同,索引會以連結串列的方式存放多個記錄指標到同一個雜湊條目中。

下面來看一一個例子。假設有如下表:
CREATE TABLE testhash (

fname VARCHAR(50) NOT NULL,

lname VARCHAR(50) NOT NULL,

KEY USING HASH( fname)) ENGINE=MEMORY;

表中包含如下資料:


假設索引使用假想的雜湊函式f(),它返回下面的值(都是示例資料,非真實資料) :
f(' Arjen')= 2323f

(' Baron')= 7437

f('Peter')= 8784

f('Vadim' )= 2458

則雜湊索引的資料結構如下:


      注意每個槽的編號是順序的,但是資料行不是。現在,來看如下查詢:

      mysql> SELECT 1name FROM testhash WHERE fname=' Peter' ;

      MySQL先計算'Peter'的雜湊值,並使用該值尋找對應的記錄指標。因為f('Peter')=8784,所以MySQL在索引中查詢8784,  可以找到指向第3行的指標,最後-步是比較第三行的值是否為'Peter', 以確保就是要查詢的行。

      因為索引自身只需儲存對應的雜湊值,所以索引的結構十分緊湊,這也讓雜湊索引查詢的速度非常快。然而,雜湊索引也有它的限制:

  •       雜湊索引只包含雜湊值和行指標,而不儲存欄位值,所以不能使用索引中的值來避免讀取行。不過,訪問記憶體中的行的速度很快,所以大部分情況下這一點對效能的影響並不明顯。
  •       雜湊索引資料並不是按照索引值順序儲存的,所以也就無法用於排序。
  •       雜湊索引也不支援部分索引列匹配查詢,  因為雜湊索引始終是使用索引列的全部內容來計算雜湊值的。例如,在資料列(A,B). 上建立雜湊索引,如果查詢只有資料列A,則無法使用該索引。
  •       雜湊索引只支援等值比較查詢,包括=、IN()、 <= > (注意<>和<=>是不同的操作)。也不支援任何範圍查詢,例如WHERE price > 100。
  •       訪問雜湊索引的資料非常快,除非有很多雜湊衝突(不同的索引列值卻有相同的雜湊值)。當出現雜湊衝突的時候,儲存引擎必須遍歷連結串列中所有的行指標,逐行進行比較,直到找到所有符合條件的行。
  •       如果雜湊衝突很多的話,一些索引維護操作的代價也會很高。例如,如果在某個選擇性很低(雜湊衝突很多)的列上建立雜湊索引,那麼當從表中刪除一一行時,儲存引擎需要遍歷對應雜湊值的連結串列中的每-一行,找到並刪除對應行的引用,衝突越多,代價越大。


      因為這些限制,雜湊索引只適用於某些特定的場合。而一旦適合雜湊索引,則它帶來的效能提升將非常顯著。舉個例子,在資料倉庫應用中有-一種經典的“星型”schema, 需要關聯很多查詢表,雜湊索引就非常適合查詢表的需求。

      除了Memory引擎外,NDB叢集引擎也支援唯一雜湊索引,且在NDB叢集引擎中作用非常特殊,但這不屬於本書的範圍。

      InnoDB引擎有一個特殊的功能叫做“自適應雜湊索引(adaptive hash index)”。  當InnoDB注意到某些索引值被使用得非常頻繁時,它會在記憶體中基於B-Tree索引之上再建立-一個雜湊索引,這樣就讓B-Tree索引也具有雜湊索引的一些優點,比如快速的雜湊查詢。這是一個完全自動的、內部的行為,使用者無法控制或者配置,不過如果有必要,完全可以關閉該功能。


      建立自定義雜湊索引。如果儲存引擎不支援雜湊索引,則可以模擬像InnoDB一樣建立雜湊索引,這可以享受- -些雜湊索引的便利,例如只需要很小的索引就可以為超長的鍵建立索引。

      思路很簡單:在B-Tree基礎.上建立-一個偽雜湊索引。這和真正的雜湊索引不是一回事,因為還是使用B-Tree進行查詢,但是它使用雜湊值而不是鍵本身進行索引查詢。你需要做的就是在查詢的WHERE子句中手動指定使用雜湊函式。

      下面是一個例項,  例如需要儲存大量的URL,並需要根據URL進行搜尋查詢。如果使用B-Tree來儲存URL,儲存的內容就會很大,  因為URL本身都很長。正常情況下會有如下查詢:

      mysql> SELECT id FROM url WHERE url="http: //ww.mysq1.com" ;

      若刪除原來URL列.上的索引,而新增-一個被索引的url_ crc 列,使用CRC32做雜湊,就可以使用下面的方式查詢:

mysql> SELECT id FROM url WHERE url="http://www.mysq1. com"
AND url_ crc=CRC32("http://www.mysq1.com");

這樣做的效能會非常高,因為MySQL優化器會使用這個選擇性很高而體積很小的基於url_ crc列的索引來完成查詢(在上面的案例中,索引值為1560514994)。即使有多個記錄有相同的索引值,查詢仍然很快,只需要根據雜湊值做快速的整數比較就能找到索引條目,然後----比較返回對應的行。另外一種方式就是對完整的URL字串做索引,那樣會非常慢。

這樣實現的缺陷是需要維護雜湊值。可以手動維護,也可以使用觸發器實現。下面的案例演示了觸發器如何在插入和更新時維護url_ crc 列。首先建立如下表:

CREATE TABLE pseudohash (

id int unsigned NOT NULL auto_ increment ,

url varchar(255) NOT NULL,

url_ crc int unsigned NOT NULL DEFAULT 0,

PRIMARY KEY(id)

);

然後建立觸發器。先臨時修改一下語句分隔符, 這樣就可以在觸發器定義中使用分號:
DELIMITER //
CREATE TRIGGER pseudohash crc_ ins BEFORE INSERT ON pseudohash FOR EACH ROW BEGINET NEW.url_ crc=crC32(NEW. url);END;/

CREATE TRIGGER pseudohash_ .CrC_ upd BEFORE UPDATE ON pseudohash FOR EACH ROW BEGINSET NEW.url_ crc=crc32(NEW.url);END;

DELIMITER ;

剩下的工作就是驗證一下觸發器如何維護雜湊索引:


      如果採用這種方式,記住不要使用SHA1()和MD5()作為雜湊函式。因為這兩個函式計算出來的雜湊值是非常長的字串,會浪費大量空間,比較時也會更慢。SHA1() 和MD5()是強加密函式,設計目標是最大限度消除衝突,但這裡並不需要這樣高的要求。簡單雜湊函式的衝突在-一個可以接受的範圍,同時又能夠提供更好的效能。

      如果資料表非常大,CRC32() 會出現大量的雜湊衝突,則可以考慮自己實現-一個簡單的64位雜湊函式。這個自定義函式要返回整數,而不是字串。一個簡單的辦法可以使用MD5()函式返回值的一部分來作為自定義雜湊函式。這可能比自己寫一個雜湊演算法的效能要差,不過這樣實現最簡單:

    

      處理雜湊衝突。當使用雜湊索引進行查詢的時候,必須在WHERE子句中包含常量值:

      mysql> SELECT id FROM url WHERE url_ crc=CRC32("http://www.mysq1. com")

      AND url="http://www.mysql.com" ;

      一旦出現雜湊衝突,另一個字串的雜湊值也恰好是15605 14994,則下面的查詢是無法正確工作的。

      mysql> SELECT id FROM url WHERE url_ crc=CRC32("http://www.mysql.com");

      因為所謂的“生日悖論”,出現雜湊衝突的概率的增長速度可能比想象的要快得多。CRC32()返回的是32位的整數,當索引有93 000條記錄時出現衝突的概率是1%。例如我們將/usr/shareldictwords中的詞匯入資料表並進行CRC32()計算,最後會有98 569行。,這就已經出現一-次雜湊衝突了,衝突讓下面的查詢返回了多條記錄:

正確的寫法應該如下:

要避免衝突問題,必須在WHERE條件中帶人雜湊值和對應列值。如果不是想查詢具體值,  例如只是統計記錄數(不精確的),則可以不帶人列值,直接使用CRC32()的雜湊值查詢即可。還可以使用如FNV64()函式作為雜湊函式,這是移植自Percona Server 的函式,可以以外掛的方式在任何MySQL版本中使用,雜湊值為64位,  速度快,且衝突比CRC32()要少很多。