哈希索引的介绍和应用

一.哈希表

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

发布了22 篇原创文章 · 获赞 5 · 访问量 1062

猜你喜欢

转载自blog.csdn.net/lighter613/article/details/102970535