Hash索引
概念
? ? ? ?基于哈希表實(shí)現(xiàn),只有匹配所有列的查詢才有效。對(duì)于每一行數(shù)據(jù),存儲(chǔ)引擎都會(huì)對(duì)所有索引列計(jì)算一個(gè)哈希碼,哈希碼是一個(gè)較小的值,不同鍵值的行計(jì)算出的哈希碼也不一樣。哈希索引將所有的哈希碼存儲(chǔ)在索引中,同時(shí)保存指向每個(gè)數(shù)據(jù)行的指針。
? ? ? ?如果多個(gè)列的哈希值相同,索引會(huì)以鏈表的方式存放多個(gè)記錄指針到同一個(gè)哈希條目中去。
舉例
CREATE TABLE `testhash` (
`fname` varchar(50) DEFAULT NULL,
`lname` varchar(50) DEFAULT NULL,
KEY `fname` (`fname`) USING HASH
) ENGINE=MEMORY;
? ? ? ?為什么用MEMORY存儲(chǔ)引擎,因?yàn)閙ysql只有MEMORY存儲(chǔ)引擎顯示支持哈希索引。
? ? ? ?表包含數(shù)據(jù):select * from testhash
假設(shè)索引使用哈希函數(shù)f()來生成哈希碼:
? ? ? ?f('Arjen')=2323
? ? ? ?f('Baron')=7437
? ? ? ?f('Peter')=8784
? ? ? ?f('Vadim')=2458
則,哈希索引的數(shù)據(jù)結(jié)構(gòu)是:
哈希表中哈希碼是順序的,導(dǎo)致對(duì)應(yīng)的數(shù)據(jù)行是亂序的。
看如下查詢:
select lname from testhash where fname ='Peter'
? ? ? ?Mysql首先計(jì)算Peter的哈希值是8784,然后到哈希索引中找到對(duì)應(yīng)的行指針,根據(jù)指針找到對(duì)應(yīng)的數(shù)據(jù)行。
? ? ? ?索引只存儲(chǔ)哈希碼及行指針,所以索引的數(shù)據(jù)結(jié)構(gòu)非常的緊湊,這也讓哈希索引查找速度非常快,但是哈希索引也有他的限制。
哈希索引限制
- 哈希索引只保存哈希碼和指針,而不存儲(chǔ)字段值,所以不能使用索引中的值來避免讀取行。不過訪問內(nèi)存中的行速度非常快(因?yàn)槭荕EMORY引擎),所以對(duì)性能影響并不大
- 哈希索引數(shù)據(jù)并不是按照索引值順序存儲(chǔ)的,所以無法用于排序
- 哈希索引不支持部分索引列查找,因?yàn)楣K饕冀K是使用索引列的全部?jī)?nèi)容來計(jì)算哈希碼。
如,在數(shù)據(jù)列(A,B)上建立哈希索引,如果查詢只有數(shù)據(jù)列A,則無法使用該哈希索引 - 哈希索引只支持等值比較查詢,包括=、IN()、<=>,不支持范圍查詢,如where price > 100
- 哈希沖突(不同索引列會(huì)用相同的哈希碼)會(huì)影響查詢速度,此時(shí)需遍歷索引中的行指針,逐行進(jìn)行比較。
- 如果哈希沖突很多,一些索引維護(hù)操作的代價(jià)會(huì)很高。
? ? ? ?如果從表中刪除一行,需要遍歷鏈表中的每一行,找到并刪除對(duì)應(yīng)行的引用,沖突越多,代價(jià)越大。
? ? ? ?總結(jié):哈希索引限制多,只適用于一定的場(chǎng)合。而一旦適合哈希索引,它帶來的性能提升將非常顯著。
自定義哈希索引
? ? ? ?在InnoDB中,某些索引值被使用的非常頻繁的時(shí)候,它會(huì)在內(nèi)存中基于B+Tree的基礎(chǔ)上再創(chuàng)建一個(gè)哈希索引,使其不必要在從根節(jié)點(diǎn)就行查找。完全自動(dòng)的內(nèi)部行為,用戶無法配置或更改。
使用場(chǎng)景
為超長的鍵創(chuàng)建哈希索引。列值太長,導(dǎo)致索引體積過大,查詢速度也會(huì)受到影響。
創(chuàng)建思路
? ? ? ?增加一個(gè)額外哈希列,將列值映射成哈希值,對(duì)哈希列進(jìn)行再進(jìn)行索引。在where條件處手動(dòng)指定使用哈希函數(shù)。
假設(shè)使用的是哈希函數(shù)hash(),查詢語句如下:
select * from table where 列B=
hash('https://blog.csdn.net/qq_26222859/article/details/1')
and 列A=‘https://blog.csdn.net/qq_26222859/article/details/1'
列B還是利用B+Tree索引進(jìn)行查找,只不過我們是利用哈希值而不是列鍵本身進(jìn)行索引。
實(shí)例
CREATE TABLE `url_hash` (
`url` varchar(255) DEFAULT NULL,
`url_crc` bigint(10) DEFAULT NULL,
KEY `HASHINDEX` (`url_crc`) USING BTREE
) ENGINE=InnoDB;
url鍵查詢
select * from url_hash where url='https://blog.csdn.net/qq_2622285'
使用mysql自帶的CRC32函數(shù)對(duì)url做哈希處理,就可以使用下面的函數(shù)查詢
select * from url_hash where url_crc=CRC32('https://blog.csdn.net/qq_2622285' ) and url='https://blog.csdn.net/qq_2622285'
mysql優(yōu)化器會(huì)選擇性能高且體積小的基于url_crc列的索引來完成查找,即使用多個(gè)相同的索引值,查找仍然很快。
但是,我們需要手動(dòng)維護(hù)crc_url哈希列,可通過觸發(fā)器在插入和更新時(shí)實(shí)時(shí)維護(hù)url_crc列,如下
CREATE DEFINER=`root`@`localhost` TRIGGER `CRC_INS` BEFORE INSERT ON `url_hash` FOR EACH ROW begin
set NEW.url_crc=crc32(NEW.url);
end;
CREATE DEFINER=`root`@`localhost` TRIGGER `CRC_UPD` BEFORE UPDATE ON `url_hash` FOR EACH ROW begin
set NEW.url_crc=crc32(NEW.url);
end;
驗(yàn)證:
insert into url_hash(url) values ('https://blog.csdn.net/qq_2622285')
select * from url_hash
update url_hash set url ='update'
select * from url_hash
select * from url_hash where url='https://blog.csdn.net/qq_2622285' and url_crc=CRC32('https://blog.csdn.net/qq_2622285')
注意,
1、where語句中必須包含url,避免哈希沖突。
2、mysql同時(shí)提供了SHA1()、MD5()兩個(gè)加密函數(shù),不要使用這兩個(gè)函數(shù)做哈希函數(shù),他們是強(qiáng)加密函數(shù),設(shè)計(jì)目標(biāo)是最大限度消除沖突,但計(jì)算的哈希值很長,浪費(fèi)空間且有時(shí)更慢。哈希沖突只要在一個(gè)可接受的范圍內(nèi)對(duì)性能影響并不大。
select SHA1('CONGZHIZHI')
select MD5('CONGZHIZHI')
空間數(shù)據(jù)索引
? ? ? ?MyISAM存儲(chǔ)引擎支持空間索引,可以用作地理數(shù)據(jù)存儲(chǔ)。和B+Tree索引不同,這類索引無需前綴查詢。空間索引從所有維度索引數(shù)據(jù)。查詢時(shí),可以有效地使用任意維度來組合查詢。必須使用Mysql的GIS相關(guān)函數(shù)如MBRCONTAINS()等來維護(hù)數(shù)據(jù)。Mysql 的GIS并不完善,大部分人不會(huì)使用到這個(gè)特性。開源關(guān)系數(shù)據(jù)庫中對(duì)GIS的解決方案做得比較好的是PostgreSQL的PostGIS。
全文索引
? ? ? ?全文索引是一種特殊類型的索引,它查找的是文本中的關(guān)鍵字,而不是直接比較索引中值。全文索引和其他類索引的匹配方式完全不一樣。它有許多需要注意的細(xì)節(jié),如停用詞、詞干、復(fù)數(shù)和布爾搜索等。全文索引更類似于搜索引擎做的事情,而不是簡(jiǎn)單的where條件匹配。