哈希索引的介紹和應用

一.哈希表

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()少;

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