數據庫——一文解析MySQL索引機制

一、前言

本文介紹MySQL索引機制,分爲四個部分:“索引引入及其優點”、“B+樹索引”、“哈希索引”、“全文索引”,一起來看看吧!

1.1 索引引入

索引(在 MySQL中也叫做“鍵(key)")是存儲引擎用於快速找到記錄的一種數據結構,這是索引的基本功能。索引對於良好的性能非常關鍵,尤其是當表中的數據量越來越大時,索引對性能的影響愈發重要,在數據量較小且負載較低時,不恰當的索引對性能的影響可能還不明顯,但當數據量逐漸增大時,性能則會急劇下降。
但是,索引卻經常被忽略,有時候甚至被誤解,所以在實際案例中經常會遇到由糟糕索引導致的問題。索引優化應該是對查詢性能優化最有效的手段了,索引能夠輕易將查詢性能提高几個數量級,“最優”的索引有時比一個“好的”索引性能要好兩個數量級。
例如:

如果在Dep_id列上建有索引,則 MySQL將使用該索引找到Dep_id爲5的行,也就是說, MySQL先在索引上按值進行查找,然後返回所有包含該值的數據行索引可以包含一個或多個列的值,如果索引包含多個列,那麼列的順序也十分重要,因爲MySQL只能高效地使用索引的最左前綴列。

創建一個包含兩個列的索引,和創建兩個只包含一列的索引是大不相間的,下面將詳細介紹.

如果使用的是ORM,是否還需要關心索引?
回答:是的,仍然需要關心索引,即使是使用對象美系映射(ORM)工具也要關心索引。
ORM工具能夠生產符合邏輯的,合法的查詢(多數時候),除非只是生成非常基本的查詢(例如僅是根據主鍵查詢),否則它很難生成適合索引的查詢。無論是多麼複雜的ORM工具,在精妙和複雜的索引面前都是"浮雲"。讀完本文後面的內容,你就會同意這個觀點的!很多時候,即使是查詢優化技術專家也很難兼顧到各種情況,更別說ORM了。

索引有很多種類型,可以爲不同的場景提供更好的性能,在 MySQL中,索引是在存儲 引擎層而不是服務器實現的。所以,並沒有統一的索引標準:不同存儲引擎的索引的 工作方式並不一樣,也不是所有的存儲引擎都支持所有類型的索引。即使多個存儲引擎支持同一種類型的索引,其底層的實現也可能不同。常用的索引有三種:B+樹索引、哈希索引、全文索引,本文分別在第二部分、第三部分、第四部分介紹。

1.2 索引優點

索引可以讓服務器快速地定位到表的指定位置。但是這井不是索引的唯一作用,到目前 爲止可以看到,根據創建索引的數據結構不同,索引也有一些其他的附加作用。

最常見的B-Tree索引,按照順序存儲數據,所以MySQL可以用來做ORDER BY和GRUP BY操作。因爲數據是有序的,所以B-Tree也就會將相關的列值都存儲在一起。最後,因爲索引中存儲了實際的列值,所以某些查詢只使用索引就能夠完成全部查詢。據此特性,總結下來索引有如下三個優點:
1.索引大大減少了服務器需要掃描的數據量;
2.素引可以幫助服務器避免排序和臨時;
3.素引可以將隨機LO變爲版序1O;

索引是最好的解決方案嗎?
索引並不總是最好的工具,總的來說,只有當素引幫助存儲引學快速查找到記錄帶來的好處大於其帶來的額外工作時,常引纔是有效的,對於非常小的表,大部分情況下簡單的全表掃描更高效。對於中到大型的表,索引就非常有效。但對於特大型的表,建立和使用索引的代價將隨之增長。這種情況下,則需要一種技術可以直接區分出查詢需要的一組數據,而不是一條記錄一條記錄地匹配,例如可以便用分區戰術。
如果表的數量種多,可以建立一個元數據信息表,用來查詢需要用到的某些特性。例如執行那些需要聚合多個應用分佈在多個表的數據的查詢,則需要記錄“哪個用戶的信息存儲在哪個表中”的元數據,這樣在查詢時就可以直接忽略那些不包含指定用戶信息的表。對於大型系統,這是一個常用的技巧,事實上, Infobright就是使用類似的實現。對於TB級別的數據,定位單條記錄的意義不大,所以經常會使塊級別元數據技術來替代索引。

二、B+樹索引

2.1 從B樹到B+樹

2.1.1 B樹

B樹(B tree)是一種平衡的多路查找樹,主要面向動態查找,通常用在文件系統中。

1、B樹引入

一棵m階的B-樹或者爲空樹,或者爲滿足下列特性的m叉樹
(1)所有的葉子結點都出現在同一層,並且不帶信息。葉子結點的雙親稱爲終端結點;
(2)樹中每個結點至多有m棵子樹;
(3)若根結點不是終端結點,則至少有兩棵子樹;
(4)除根結點之外的所有非終端結點至少有⌈m/2⌉棵子樹;
(5)所有的非終端結點都包含以下數據: (n,A0,K1,A1,K2,…,Kn,An)
其中,n(⌈m/2⌉-1≤n≤m-1)爲關鍵碼的個數,Ki(1≤i≤n)爲關鍵碼,且Ki<K(i+1)(1≤i≤n-1),Ai(0≤i≤n)爲指向子樹根結點的指針,且指針Ai所指子樹中所有結點的關鍵碼均小於K(i+1)大於Ki。
一般情況下,B樹的葉子結點可以看做是外部結點(即查找失敗)的結點,通常稱爲外結點。實際上這些結點不存在,指向這些結點的指針爲空。所以,B樹的葉子可以不畫出來。因爲葉子都出現在同一層上,所以B樹也是樹高平衡的。另外,每個結點中關鍵碼的個數爲子樹的個數減1。
B樹是2-3樹的推廣,2-3樹是一個3階B樹。通常B樹中的一個結點的大小能夠填滿一個磁盤頁,存儲在B樹中的指針實際上是包含其孩子結點的塊號,每個結點一般允許100個或者更多個孩子。

2、B樹查找

B.樹的查找類似於2-3樹的查找,所不同的是B樹的每個結點上是多關鍵碼的有序表,在到達某個結點時,先在有序表中查找,若找到,則查找成功;否則,按照指針信息到相應的子樹中查找。當到達葉子結點時,則說明樹中沒有對應的關鍵碼,查找失敗。在

B樹上的查找過程是一個順指針查找結點和在結點中查找關鍵碼交叉進行的過程。比如,上圖中查找關鍵碼爲53的記錄。首先,從root指向的根結點a開始,根結點a 中只有一個關鍵碼,且53大於它,因此,按根結點a的指針域A1到結點c去查找,c結點有兩個關鍵碼(43、78),而53大於43小於78,應按結點c指針域A1到結點g去查找,在結點g中順序比較關鍵碼,找到關鍵碼53。
所以,在B樹上進行查找包含兩種基本操作:(1)在B樹中查找結點;(2)在結點中查找關鍵碼。由於B樹通常存儲在磁盤上,則前一個查找操作(指在B樹中查找結點)是在磁盤上進行的,而後一個查找操作(指在結點中查找關鍵碼)是在內存中進行的,即在磁盤上找到某結點後,先將結點的信息讀入內存,然後再查找等於k的關鍵碼。顯然,在磁盤上進行一次查找比在內存中進行一次查找耗費的時間多得多,因此,在磁盤上進行查找的次數,即待查關鍵碼所在結點在B樹的層數,是決定B樹查找效率的首要因素。

3、B樹插入

B樹的插入是2-3樹插入的推廣
假定要在m階B樹中插入關鍵碼key,設n=m-1,即n爲結點中關鍵碼數目的最大值,B樹的插入過程如下:
(1)定位:查找插人位置。由於是在終端結點中插入,因此要確定它屬於哪個終端結點。定位的結果是返回了key所屬終端結點的指針p。若p中的關鍵碼個數小於n,則 直接插人關鍵碼key;否則,結點p的關鍵碼個數溢出,執行“分裂一提升”過程。
(2)分裂一提升:將結點p“分裂”成兩個結點,分別是p1和p2,把中間的關鍵碼k“提升”到父結點,並且k的左指針指向p1,右指針指向p2。如果父結點的關鍵碼個數也溢出,則繼續執行“分裂一提升”過程。顯然,這種分裂可能一直上傳,如果根結點也分裂了,則樹的高度增加了一層 。
這個插入過程保證所有的結點至少是半滿的。例如,當一個4階B樹的內部結點已滿時,將會有5個子女。這個結點分裂成爲兩個結點,每個結點包含兩個關鍵碼,這樣就 保持了B樹的特性。下圖給出了在3階B樹中進行插入的示例:

4、B樹刪除

B樹的刪除是2-3樹刪除的推廣。
設在m階B樹中刪除關鍵碼key。首先要找到key的位置,即“定位”。定位的結果是返回了key所在結點的指針q,假定key是結點q中第i個關鍵碼K,若結點q不是終結點,則用Ai所指的子樹中的最小鍵值x來“替換”Ki。由於x所在結點一定是終端結點,這樣,刪除問題就歸結爲在終端結點中刪除關鍵碼。
如果終端結點中關鍵碼的個數大於⌈m/2⌉-1,則可直接刪除該關鍵碼,如下圖:

如果在終端結點中刪除一個關鍵碼後其關鍵碼的個數不足⌈m/2⌉-2,則不符合m階B樹的要求,需要從兄弟結點借關鍵碼或合併結點,以保證B樹的特性。具體分兩種情況:
(1)兄弟夠借,查看相鄰的兄弟結點,如果兄弟結點有足夠多的記錄(多於⌈m/2⌉),就從兄弟結點借來一個記錄,將借來的關鍵碼“上移”到被刪結點的雙親結點中,同時將雙親結點中的相應關鍵碼“下移”到被刪結點中。這樣做的目的是爲了儘可能地延遲由於刪除而引起的結點中關鍵碼個數的下溢。
(2)兄弟不夠借。如果沒有一個兄弟結點可以把記錄借給這個記錄太少的被刪結點,那麼被刪結點就必須把它的關鍵碼讓給一個兄弟結點,即執行“合併”操作,並且從樹中把這個空結點刪除。兄弟結點當然有空間,因爲兄弟結點至多半滿,合併後被刪結點的雙親少了一個結點,所以要把雙親結點中的一個關鍵碼“下移”到合併結點中。如果被刪結點的雙親結點中的關鍵碼的個數沒有下溢,則合併過程結束;否則,雙親結點也要進行借關鍵碼或合併結點。顯然,合併過程可能會上傳到根結點,如果根結點的兩個子女合併到一起,則B樹就會減少一層。
下圖給出了在3階B樹中刪除關鍵碼時,出現被刪結點中關鍵碼個數發生下溢的情況以及處理示意圖。

2.1.2 B+樹

1、B+樹引入

B+樹是B樹的變體,是由B樹和索引順序訪問方法(ISAM,Indexed Sequential Access Methed)演變而來,B+樹是爲磁盤或其他直接存取輔助設備設計的一種平衡查找樹。在B+樹中,所有記錄都是按鍵值的大小順序存放在同一層的葉子結點上,由各葉子節點指針進行拼接。

一棵m階的B+樹在結構上與m階的B樹相同,但在關鍵碼的內部安排上有所不同。具體如下:
(1)具有m棵子樹的結點含有m個關鍵碼,即每一個關鍵碼對應一棵子樹;
(2)關鍵碼K,是它所對應的子樹的根結點中的最大(或最小)關鍵碼;
(3)所有的終端結點中包含了全部關鍵碼信息,及指向關鍵碼記錄的指針;
(4)各終端結點按關鍵碼的大小次序鏈在一起,形成單鏈表,並設置頭指針 與B_樹類似,在B樹中,結點內的關鍵碼仍然有序排列,並且對同一結點內的任意 兩個關鍵碼K和K,若K,<K,則K,小於K,對應的子樹中的所有關鍵碼。
與二叉排序樹和2-3樹最顯著的區別是B+樹只在終端結點存儲記錄,內部結點存儲關鍵碼,但是這些關鍵碼只是用於引導查找的。這意味着內部結點在結構上與終端結點有顯著的區別。內部結點存儲關鍵碼用於引導查找,把每個關鍵碼與一個指向子女結點 的指針相關聯;終端結點存儲實際記錄,在B+樹純粹作爲索引的情況下則存儲關鍵碼和指向實際記錄的指針。一個B+樹的終端結點一般鏈接起來,形成一個鏈表,這樣,通過訪問鏈表中的所有終端結點,就可以按照排序的順序遍歷全部記錄。
例如,下圖所示爲一棵3階的B+樹,通常在B+樹上有兩個頭指針,一個指向根結點,另一個指向關鍵碼最小的終端結點。因此,可以對B+樹進行兩種查找操作:一種是 從最小關鍵碼起順序查找,另一種是從根結點開始隨機查找。

在B+樹上進行隨機查找、插入和刪除的過程基本上與B樹類似。除了查找必須一直到達終端結點外,在一棵B樹中的查找幾乎與在一棵B樹中的查找完全一樣。即使在一個內部結點找到了待查找的關鍵碼值,但它只是用來引導索引的,並不提供對實際記錄的訪問,所以在B+樹查找:B+樹中查找時,必須到達包含有該關鍵碼值的終端結點。
B+樹插入:B+樹的插入僅在終端結點上進行,當結點中的關鍵碼個數大於m時要分裂成兩個結點,並且它們的雙親結點中應同時包含這兩個結點中的最大關鍵碼。
B+樹刪除:B+樹的刪除也僅在終端結點上進行,當終端結點中的最大關鍵碼被刪除時,其在非終端結點中的值可以作爲一個“分界關鍵碼”存在。若因刪除而使結點中關鍵碼的個數少於 時,和兄弟結點的合併過程和B樹類似。
B+樹範圍查找:B+樹特別適合範圍查找。一旦找到了範圍中的第一個記錄,通過順序處理結點中的 其餘記錄,然後繼續下去,儘可能地深入終端結點,就可以找到範圍中的全部記錄。

2、B+樹查找

先來看一個B+樹,其高度爲2,每頁可存放4條記錄,扇出(fan out)爲5,如下圖。
所有記錄都在葉子節點上,並且都是順序存放的,如果用戶從最左邊的葉子節點開始順序遍歷,可以得到所有鍵值的順序排序:5、10、15、20、25、30、50、55、60、65、70、75、80、85、90。

2.1.3 小結

B樹和B+樹統稱爲B樹,是需要插入、刪除和關鍵碼範圍檢索的應用程序的標準組織方法,解決了實現基於磁盤的檢索時遇到的下列所有問題:
(1)B樹總是樹高平衡的,所有葉結點都在同一層;
(2)査找、插入和刪除等操作隻影響一些結點(即磁盤頁),因此性能很好;
(3)B樹把相關的記錄放在同一個磁盤頁中,從而利用了訪問局部性原理;
(4)B樹保證樹中至少有一定比例的結點是滿的,這樣能夠改進空間的利用率,同時 在查找和更新操作期間減少對磁盤的讀取次數。

2.2 B+樹索引

人們談論索引的時候,如果沒有特別指明類型,那多半說的是B+Tree索引(因爲大多數MySQL引擎都支持這種索引),它使用Tree數據結構來存儲數據。

存儲引擎以不同的方式使用B+Tree索引,性能也各有不同,各有優劣。例如, MyISAM 使用前綴壓縮技術使得索引更小,但 InnoDB則按照原數據格式進行存儲。再如MyISAM 索引通過數據的物理位置引用被索引的行,而 InnoDB則根據主鍵引用被索引的行。

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

B-Tree索引能夠加快訪問數據的速度,因爲存儲引擎不再需要進行全表掃描來獲取需要的數據,取而代之的是從索引的根節點開始進行搜索,根節點存放了指向子節點的指針,存儲引擎根據這些指針向下層查找。通過比較節點頁的值和要查找的值可以找到合適的指針進入下層子節點,這些指針實際上定義了的上限和下限。最終存儲引擎要麼是找到對應的值,要麼該記錄不存在。

其中,葉子節點比較特別,它們的指針指向的是被索引的數據,而不是其他的節點頁(ps:不同引擎的“指針”類型不同),樹的深度和表的大小直接相關。

B-Tree對索引列是順序組織存儲的,所以很適合查找範圍數據。例如,在一個基於文本域的索引樹上,按字母順序傳遞連續的值進行查找是非常合適的,所以像“找出所有以I到K開頭的名字”這樣的查找效率會非常高。

請注意,索引對多個值進行排序的依據是CREATE TABLE語句中定義索引時列的順序。看一下最後兩個條目,兩個人的姓和名都一樣,則根據他們的出生日期來排列順序。

可以使用B-Tree索引的查詢類型。 B-Tree索引適用於全鍵值、鍵值範圍或鍵前綴查找。 其中鍵前綴查找只適用於根據最左前綴的查找,前面所述的索引對如下類型的查詢有效。

全值匹配
全值匹配指的是和索引中的所有列進行匹配,例如前面提到的索引可用幹查找姓名爲Cuba Allen、出生於1960-01-01的人。

匹配最左前綴
前面提到的索引可用於查找所有姓爲Alen的人,即只使用索引的第一列。

匹配列前綴
也可以只匹配某一列的值的開頭部分,例如前面提到的索引可用於查找所有以J開頭的姓的人,這裏也只使用了索引的第一列。

匹配範圍值
例如前面提到的索引可用於查找姓在Aen和Barrymore之間的人,這裏也只使用了索引的第一列。

精確匹配某一列並範圍匹配另外一列
前面提到的索引也可用於查找所有姓爲Allen,並且名字是字母K開頭(比如Kim、Karl等)的人,即第一列last_name全匹配,第二列first_nane範圍匹配。

只訪問索引的查詢
B-Tree通常可以支持“只訪問索引的查詢”,即查詢只需要訪問索引,而無須訪問數據行,後面我們將單獨討論這種“覆蓋索引”的優化。

因爲索引樹中的節點是有序的。所以除了按值查找之外,索引還可以用於查詢中的ORDER BY操作(按順序查找),一般來說,如果B-Tree可以按照某種方式查找到值,那麼也可以按照這種方式用於排序。所以,如果ORDER BY子句滿足前面列出的幾種查詢類型,則這個索引也可以滿足對應的排序需求。

附:一些關於 B-Tree索引的限制
(1)如果不是按照索引的最左列開始查找,則無法使用索引。例如上面例子中的索引無法用於查找名字爲Bill的人,也無法查找某個特定生日的人,因爲這兩列都不是最左數據列。類似地,也無法查找姓氏以某個字母結尾的人;
(2)不能跳過索引中的列,也就是說,前面所述的索引無法用於查找姓爲 Smith並且在某個特定日期出生的人,如果不指定名(first_name),則 MySQL只能使用索引的第一列;
(3) 如果查詢中有某個列的範圍查詢,則其右邊所有列都無法使用索引優化查找。例如 有查詢
WHERE last name=‘Smith’ AND first_name LIKE ‘J%’ AND dob=1976-12-23,
這個查詢只能使用索引的前兩列,因爲這裏LIKE是一個範圍條件(但是服務器可以把其餘列用於其他目的)。如果範圍查詢列值的數量有限,那麼可以通過使用多個等於條件來代替範圍條件。

到這裏讀者應該可以明白,前面提到的索引列的順序是多麼的重要:這些限制都和索引 列的題序有關

三、哈希索引

哈希索引( 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;

注意,在MySQL中,只有Memory引擎顯示支持哈希索引,也是默認索引類型。所以我們這裏將testhash表的engine設置爲Memory,就是使用了哈希索引。

在這裏插入圖片描述
假設索引使用假想的哈希函數f(),它返回下面的值(都是示例數據,非真實數據):

f('Arjen') = 2323
f('Baron') = 7437
f('Peter') = 8784
f('Vadim') = 2458

則哈希索引的數據結構如下:

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

MySQL先計算’Peter’的哈希值,並使用該值尋找對應的記錄指針,因爲f(‘Peter’)= 8784,所以 MySQL在索引中查找8784,可以找到指向第3行的指針,最後一步是比較第三行的值是否爲 ‘Peter’,以確保就是要查找的行。

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

附:哈希散列的限制 ·
(1)哈希索引只包含哈希值和行指針,而不存儲字段值,所以不能使用索引中的值來避免讀取行。不過,訪問內存中的行的速度很快,所以大部分情況下這一點對性能的影響並不明顯。
(2)哈希索引數據並不是按照索引值順序存儲的,所以也就無法用於排序。
(3)哈希索引也不支持部分索引列匹配查詢,因爲哈希索引始終是使用索引列的全部內容來計算哈希值的。例如,在數據列(A,B)上建立哈希索引,如果查詢只有數據列A,則無法使用該索引。
(4)哈希索引只支持等值比較查詢,包括=、IN()、<=>,也不支持任何範圍查詢,例如 WHERE price>100。
(5)訪問哈希索引的數據非常快,除非有很多哈希衝突(不同的索引列值卻有相同的哈希值),當出現哈希衝突的時候,存儲引擎必須遍歷鏈表中所有的行指針,逐行進行比較,直到找到所有符合條件的行。
(6)如果哈希衝突很多的話,一些索引維護操作的代價也會很高。例如,如果在某個選擇性很低(哈希衝突很多)的列上建立哈希索引,那麼當從表中刪除一行時,存儲引擎需要遍歷對應哈希值的鏈表中的每一行,找到並刪除對應行的引用,衝突越多,代價越大。

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

自適應哈希索引
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://www.baidu.com";

若刪除原來URL列上的索引,而新增一個被索引的url_crc列,使用CRC32做哈希,就可以使用下面的方式查詢:

mysql> select id FROM url WHERE url="http://www.baidu.com" AND url_crc=CRC32("http://www.baidu.com");

這樣做的性能會非常高,因爲 MySQL優化器會使用這個選擇性很高而體積很小的基於
url_crc列的索引來完成查找(在上面的案例中,索引值爲xxxxxx)。即使有多個記錄有相同的索引值,查找仍然很快,只需要根據哈希值做快速的整數比數就能找到索引條目,然後一一比較返回對應的行。另外一種方式就是對完整的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)
);

然後創建觸發器,先臨時修改一下語句分隔符,這樣就可以在觸發器定義中使用分號

create trigger pseudohash_crc_ins before insert on pseudohash 
for each row  begin  set  new.url_crc=crc32(new.url);
end;
create trigger pseudohash_crc_upd before update on pseudohash 
for each row begin set new.url_crc=crc32(new.url);
end;

剩下的工作就是驗證一下觸發器如何維護哈希索引

insert into pseudohash (url)  values("http://www.mysql.com");
select * from pseudohash;
update pseudohash set url = 'http://www.mysql.com/' where id = 1;
select * from pseudohash;

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

哈希衝突

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

select conv (right(md5('http://www.mysql.com/'),16),16,10) as HASH64;

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

select id from url where url_crc=CRC32("http://www.mysql.com") and url="http://www.mysql.com";

一旦出現哈希衝突,另一個字符串的哈希值也恰好是1560514994,則下面的查詢是無法正確工作的。

select id from url where url_crc=CRC32("http://www.mysql.com");

因爲會出現"生日悖論",出現哈看衝突的概率的增長速度可能比想象的要快得多.CRC32()返回的是32位的整數,當索引有93000條記錄時出現衝突的概率是1%,例如我們將/usr/share/dict/words中的詞導入數據表並進行CRC32()計算,最後會有98569行。這就已經出現一次哈希衝突了,衝突讓下面的查詢返國了多條記錄:

select word,crc from words where crc=CRC32('gnu');

正確的寫法應該如下:

select word,crc from words where crc=CRC32('gnu') and word='gnu';

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

四、全文索引

全文索引是一種特殊類型的索引,它查找的是文本中的關鍵詞,而不是直接比較索引中的值。

全文搜索和其他幾類索引的匹配方式完全不一樣。它有許多需要注意的細節,如停用詞、詞幹和複數、布爾搜索等,全文索引更類似於搜索引擎做的事情,而不是簡單的WHERE條件匹配。

在相同的列上同時創建全文索引和基於值的B-Tree索引不會有衝突,全文索引適用於 MATCH AGAINST操作,而不是普通的WHERE條件操作。

五、小結

本文介紹MySQL索引機制,屬於MySQL索引內容入門級博客,全文分爲四個部分:“索引引入及其優點”、“B+樹索引”、“哈希索引”、“全文索引”,一步步由淺入深介紹Mysql的三種索引,其中B+樹索引最重要,希望對MySQL初學者有用。

天天打碼,天天進步!

在這裏插入圖片描述

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