一.哈希表
1.概念
哈希表又稱散列表(Hash table),是根據關鍵碼值(Key value)而直接進行訪問的數據結構。也就是說,它通過把關鍵碼值映射到表中一個位置來訪問記錄,以加快查找的速度。這個映射函數叫做散列函數,存放記錄的數組叫做散列表。
給定表M,存在函數f(key),對任意給定的關鍵字值key,代入函數後若能得到包含該關鍵字的記錄在表中的地址,則稱表M爲哈希(Hash)表,函數f(key)爲哈希(Hash) 函數。
二.哈希索引
1.概念
哈希索引(Hash Index)給予哈希表實現,只有緊缺匹配索引所有列的查詢纔有效.對於每一行數據,存儲引擎都會對所有的索引列計算一個哈希碼(Hash Code),哈希碼是一個較小的值,並且不同鍵值的行計算出來的哈希碼也不一樣.哈希索引將所有的哈希碼存儲在索引中,同時在哈希表中保存指向每個數據行的指針。
在 MySQL 中,只有 Memory 引擎顯式支持哈希索引.這也是 Memory 引擎表的默認索引,Memory 引擎也支持 B-Tree 索引.值的一提的是,Memory 引擎是支持非唯一哈希索引的,這在數據庫世界裏面是比較與衆不同的.如果多個列的哈希值相同,索引會以鏈表的方式存放多個記錄指針到同一個哈希條目中.
2.舉例
-- 給列fname 創建索引 基於memory引擎
create table testhash(
fname varchar(50) not null,
lname varchar(50) not null,
key using hash(fname)
) engine = memory;
-- 插入數據
insert into testhase('aaa','AAA'),('bbb','BBB'),('ccc','CCC'),('ddd','DDD');
-- 查詢數據
select * from testhash;
-- 數據列表
fname lname
aaa AAA
bbb BBB
ccc CCC
ddd DDD
-- 假設哈希函數f(),它返回的哈希碼如下
f('aaa')= 2323
f('bbb')= 7437
f('ccc')= 8785
f('ddd')= 5565
-- 則哈希索引的數據結構如下(每個槽的編號是順序的,但是數據記錄行不是)
槽(slot) 值(vaule)
2323 指向第一行的指針
5565 指向第四行的指針
7437 指向第二行的指針
8785 指向第三行的指針
-- 數據查詢示例
select * from testhash where fname = 'aaa'
查詢流程:
1.計算 'aaa' 的哈希值 2323
2.使用該哈希值找到指針第一行的指針
3.找到該行比較值是否是'aaa',以確保就是要查找的行
4.返回記錄
3.哈希索引的限制
a.只包含哈希值和指針,不存儲字段值,不能使用索引中的值來讀取行
b.哈希索引 slot 不是按照索引值(value)順序來存儲的,所以無法排序(理解歧義)
c.哈希索引不支持部分索引列匹配查找,它是按照索引列的全部內容來計算哈希值;如果設備了兩個索引列,必須要用兩個索引列去查找
d.哈希索引只支持等值查詢,不支持範圍查詢(支持 = , in() , <=> )
e.訪問哈希數據速度快;起衝突時(不同的索引列值具有相同的哈希碼 slot 值),這時只能存儲引擎表裏鏈表中所有的行指針去比較索引列值,返回記錄
f.哈希衝突越多,後期維護越大,不同的索引列值具有相同的哈希值,產生哈希衝突,刪除記錄,也要一一比較再刪除哈希值鏈表中的記錄
因爲這些限制,哈希索引只支持特定的場合,而一旦適應哈希索引,則性能將有顯著的提升.
三.自定義哈希索引
InnoDB引擎有一個特殊的功能叫做"自適應哈希索引(adapitive hash index)".當InnoDB注意到某些索引值被使用的非常頻繁,它會在內存的中基於B-Tree索引上再創建一個哈希索引,這樣讓B-Tree索引也具有哈希索引的一些優點,比如快速的哈希查找(此行爲完全自定,是內部的自動行爲,用戶無法控制或者配置,可以關閉改功能).
1.創建思路
在B-Tree基礎上創建一個僞哈希索引,則可以模擬像InnoDB一樣創建哈希索引,好處是隻需要很小的索引就可以爲超長的鍵創建索引.
2.示例
-- B-Tree 來存儲url(本身數據很長,創建索引查詢速度不高)
select * from test_hash_demo where url = "http://www.baidu.com";
-- 刪除原來的url索引,新增一個索引列 url_crc,使用CRC32做哈希,查詢如下
select * from test_hash_demo where url = "http://www.baidu.com" and url_crc = CRC32("http://www.baidu.com");
這樣做的新能會會非常高,mysql優化器會使用這個選擇性高體積小的基於url_crc列的索引來完成查找,即使有多個記錄相同的索引值,查找仍然快,只需要根據哈希值做快速的比較,然後一一比較對應的行,返回記錄;
缺陷是需要維護哈希值.可以手動維護,也可以觸發器維護
3.觸發器維護
-- 創建表
create table pseudohash(
id int unsigned not null auto_increment,
url varchar(255) not null,
url_crl int unsigned not null default 0,
primary key (id)
);
-- 創建出發器
delimiter //
create trigger pseudohash_crc_ins before insert into 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 pesudohash;
-- 數據列表
id url url_crc
i http://www.mysql.com 1560514994
-- 修改數據
update pseudohash set url = 'http://www.mysql.com/' where id = 1;
select * from pesudohash;
-- 數據列表
id url url_crc
i http://www.mysql.com/ 158250469
採用自定義哈希索引不要採用SHA1() 和MD5作爲哈希函數,這兩個函數計算出來的哈希值會是非常長的字符串,浪費空間,比較會更慢
4.截取MD5()函數返回值
如果數據表非常大,crc32()會出現大量的哈希衝突,可以使用MD5()函數返回的一部分作爲自定義哈希函數
select conv(right(md5('http://www.mysql.com/'),16),16,10) as HASH64;
-- 數據列表
HASH64
976117372318281581
當使用哈希索引進行查詢是,一定要在where子句包含常量值,這樣纔不會引起哈希衝突;
crc32()返回的是32位的整數,當索引有9300條記錄時出現的概率是1%
-- 錯誤寫法
select word,crc from words where crc = crc32('gnu');
-- 數據列表
word crc
codding 1774765869
gnu 1774765869
-- 正確寫法
select word,crc from words where crc = crc32('gnu') and word = 'gnu';
要避免衝突,必須再where條件中帶入哈希值和對應列值;
也可以用FNV64()函數作爲哈希函數,可以以插件方式在任何mysql版本中使用,哈希值爲64位,速度快,衝突比crc32()少;