前面的几篇文章已经将磁盘管理和内存 buffer pool 管理的内容都介绍完了,接下来继续向上一层,来介绍关于 access method 的内容。
成都创新互联专注于长寿网站建设服务及定制,我们拥有丰富的企业做网站经验。 热诚为您提供长寿营销型网站建设,长寿网站制作、长寿网页设计、长寿网站官网定制、成都小程序开发服务,打造长寿网络公司原创品牌,更为您提供长寿网站排名全网营销落地服务。
access method 主要是介绍一些数据结构,例如 Hash Table 和 Tree。这些数据结构可以用来做表的索引,以及一些在 sql 计算时的临时数据结构。
在设计和使用这些数据结构时,需要注意两个问题,一是数据的组织,怎样将数据组织在内存或者磁盘中,并且能够高效的访问;二是数据结构的并发访问问题,需要保证其在多线程环境下的数据安全。
今天先来看一下在数据库里面频繁被使用,以及数据结构中也会经常涉及到的哈希表。
Hash Table 是一个无序的 key 到 value 的映射实现,它使用一个哈希函数计算数据存储到数组中(槽位)的位置,并且平均情况下,能够在 O(1) 的时间内访问元素。
如下图所示,我们通过一个 hash 函数,计算 key 在数组中中的下标,然后 key 对应了具体的 value。当查找数据时,也需要计算哈希值,然后定位到 key 所在的位置进行取值。
从前面的描述中可以看到,哈希函数在 Hash Table 中的作用至关重要。
哈希函数可以将任意类型的 key 转换为一个代表这个 key 的整数,在理想的情况下,我们希望任意不同的 key 经过哈希函数计算出来的值都是不同的,但在实际的哈希函数实现中,这几乎是不太可能的。
如果不同的 key 通过哈希函数计算出来的值是一样的,这种情况叫做哈希冲突(conflict),又叫哈希碰撞(collision)。
一般来说,计算哈希的速度越快,哈希冲突的概率越大,反之则越低,这就需要在实际设计时进行权衡。
数据库中常见的哈希函数实现有下列这几种:
接下来了解一下几种静态哈希的实现方式,所谓静态,一般是指哈希表的容量大小是固定的,所以能够存储数据的上限也是确定的,实际使用并不多,可以做参考。
Linear Probe Hash
线性探测(Linear Probe)是一种比较直观简洁的 Hash Table 实现方式了。
其基本思路是如果映射之后的 key 存储的位置已经被占用了,那么它会依次遍历数组,直到找到一个空闲的位置插入数据。
如下,新插入的数据 E 通过计算后,其位置在 A 的位置。
但是 A 处已经有数据了,此时发生了冲突,所以会向后遍历,然后找到 D 之后的空闲位置插入。
删除的逻辑比较类似,也是通过哈希函数计算 key 的位置,然后找到对应的数据并删除。如果 key 并不在原来的位置上,那么需要像插入一样遍历,直到找到目标 key。
删除一般有两种做法,一是直接在删除的位置设置一个墓碑值,表示其已被删除,二是移动其他的元素来填充删除的位置,这种方式并不常用。
重复 key哈希表中对于重复的 key 的处理方式一般有两种,一是通过一个 value 链表的方式,将同一个 key 的多个元素串联起来。
这种方式比较简单直观,另一种方式是重复存储,把每个 key 都当做是独立的,插入方式和上面描述的完全一致。
Robin Hood Hash
robin hood 哈希类似于线性探测,并有一定的改进。
它会记录每个 key 与其原始 hash 映射的位置的距离,例如下图中的 A,因为插入前没有任何数据,不存在哈希冲突,所以 A 记录的距离是 0。
如果此时插入数据 C,如果 C 映射的位置也是 A,这样就产生了哈希冲突,于是向下探测一位,将 C 插到 A 之后的位置,这时候 C 与其原始映射位置的距离就是 1。
当又有新的 key 插入时,如果产生了冲突,那么继续向后探测,并且比较映射距离的值,如果新插入的 key 的距离大于该位置的值,则将新的 key 插入。
例如上面的这个例子,如果新插入的 E 映射到了 A 的位置,此时 E 的距离是 0,和 A 的距离相等,继续向下,此时 E 的距离是 1,和 C 相等,又继续,此时 E 变为 2,大于了 D 的距离 1,于是将 E 插入到 D 的位置,并且将 D 挪到到下一个空闲的位置。
Cuckoo Hash
cuckoo hash 使用多个哈希表,并且每个哈希表使用一个不同 seed (随机种子)的哈希函数。当插入数据时,对 key 轮流用每个哈希函数都计算哈希值,如果对应的哈希表有空闲空间,则直接插入。
例如下面的例子,使用了两个哈希表,插入 key A 的时候,计算两个哈希值,并且查询到第一个哈希表有空闲,则直接将数据插入到哈希表 1 中。
如果此时在插入一条数据 B,如果在哈希表 1 中有冲突,但是在哈希表 2 中没有冲突,则将数据插入到哈希表 2 中。
如果此时再插入一个 key C,经过哈希函数计算后,发现它和两个哈希表都有冲突。
这时候需要选择一个哈希表,将其中的 key 先拿出来,腾一个位置给 C,例如可以将 C 插入到 A 的位置。然后对 A 再计算哈希值,如果 A 在哈希表 2 中没有冲突,则直接将 A 插入到哈希表 2 中。
cuckoo hash 在 Github 上也有对应的一些实现:https://github.com/efficient/libcuckoo
前面提到的这几种哈希表的实现方式,都可以认为是静态的,即哈希表中能存储多少数据,是一开始就确定下来的,并不会涉及到扩容。
但实际环境中,我们大多数时候都希望哈希表是可以随着数据量的增长而扩张的,下面再介绍几种更常用的,可以自动动态扩容的哈希表实现。
Chained Hash
链式哈希将一个哈希表通过多个 bucket 来维护,如果出现了哈希冲突,则将相同的 key 放到同一个 bucket 中,如果 bucket 超过了规定的容量,则以链表串联起一个新的 bucket。
每个 bucket 一般会有一个指针,标识其位置,当一个 key 经过 hash 之后,可以通过这个指针找到它所属的 bucket。
extendible hash(可扩展哈希)和 chained hash 比较类似,都使用到了bucket 这个概念,同时也会有一个执行 bucket 的指针数组。
与之不同的是,extendible hash 将 key 计算哈希值后,会将其值转换为一个二进制表示,然后它会维护一个 global counter,记录定位到 bucket 指针数组,需要取 key 的二进制的多少位;每个 bucket 也有一个 counter,记为 local counter,表示的是定位到该 bucket 需要二进制的多少位。
例如上面的例子,第一个 bucket 的 counter 是 1 ,当 key 定位到该 bucket 的时候,需要取 key 的前一位。而 bucket 2 和 3 的 counter 都是 2,需要取二进制的前两位。
如果需要查询数据,先对 key 计算哈希值,例如下图中的 A,计算其哈希值后,global counter 是 2,所以只需要取前两位即可,然后通过 bucket 的指针获取到 bucket 的位置,遍历其中的数据进行查找。
如果需要新插入数据,则和查找的过程类似,同样计算哈希值,然后找到对应的 bucket,将数据放到 bucket 的空闲位置即可。
如果对应的 bucket 没有空闲的位置了,这时候需要进行 split 分裂操作。
首先将 global counter 的值增加 1,然后新建一个 bucket,将旧的对应的那个 bucket 的 counter 也增加 1,并且让新的 bucket 的 counter 等于这个值。然后重新通过这个 key 的二进制位来移动旧的 bucket 中的数据。
例如下面的例子,需要插入 key C,但是它所映射的 bucket 已经满了。
此时将 global counter 增加为 3,并且新增一个 bucket,counter 为旧的值加一,也为 3。然后将旧的那个 bucket 按照新的进制位重新存放,此时 bucket 中就有空闲的位置了。
更详细内容可以参考:https://zhuanlan.zhihu.com/p/375039823
Linear Hash
前面提到的 extendible hash 在分裂的时候,需要扩展 bucket 指针数组(又叫 slot array),这时候一般需要对哈希表加全局锁,以防止并发读写冲突。但是这样锁的粒度较大,容易造成哈希表的读写瓶颈。
另一种 linear hash 会维护一个分裂指针 split pointer,当有任意的 bucket 分裂时,split pointer 指向的 bucket 也会分裂。这种方式介绍不多,实际使用应该也较少,就不详细介绍了。
哈希表是一个高效的数据结构,大多时候能够在 O(1) 的情况下插入和查询数据,在数据库系统中,有很多地方都使用到了哈希表,例如前面提到的 page table,page directory,以及执行 sql 查询时一些用于 join 的临时数据结构。
但是哈希表的应用场景也有限,因为它存储的所有 key 都是无序的,这样虽然适合点查,但是无法进行范围扫描,在更加通用的场景下,数据库中的表索引使用最广泛的还是 B+ 树。
名称栏目:CMU 15445 学习之Hash Table
链接地址:http://www.mswzjz.cn/qtweb/news2/244352.html
攀枝花网站建设、攀枝花网站运维推广公司-贝锐智能,是专注品牌与效果的网络营销公司;服务项目有等
声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 贝锐智能