Hash Tables
Last updated
Last updated
本节开始之前,先看一下目前课程的进度状态:
为了支持 DBMS 更高效地从 pages 中读取数据,DBMS 的设计者需要灵活运用一些数据结构及算法,其中对于 DBMS 最重要的两个是:
Hash Tables
Trees
它们可能被用在 DBMS 的多个地方,包括:
Internal Meta-data
Core Data Storage
Temporary Data Structures
Table Indexes
在做相关的设计决定时,通常需要考虑两个因素:
Data Organization:如何将这些数据结构合理地放入 memory/pages 中,以及为了支持更高效的访问,应当存储哪些信息
Concurrency:如何支持数据的并发访问
Hash Table 是 associative array/dictionary ADT 的实现,它将键映射成对应的值。简介这里不再赘述,可参考 MIT 6.006 Dictionary Problem & Implementation。
Hash Table 主要分为两部分:
Hash Function:
How to map a large key space into a smaller domain
Trade-off between being fast vs. collision rate
Hashing Scheme:
How to handle key collisions after hashing
Trade-off between allocating a large hash table vs. additional instructions to find/insert keys
由于 DBMS 内使用的 Hash Function 并不会暴露在外,因此没必要使用加密(cryptographic)哈希函数,我们希望它速度越快,collision rate 越低越好。目前各 DBMS 主要在用的 Hash Functions 包括:
Linear Probe Hashing 的主要原理在 MIT 6.006 中已经介绍,这里不赘述。
当 keys 可能出现重复,但 value 不同时,有两种做法:
Separate Linked List
Redundant Keys
如下图所示:
通常为了减少 collision 和 comparisons,Hash Table 的大小应当是 table 中元素量的两倍以上。
Robin Hood Hashing 是 Linear Probe Hashing 的变种,为了防止 Linear Probe Hashing 出现连续区域导致频繁的 probe 操作。基本思路是 “劫富济贫”,即每次比较的时候,同时需要比较每个 key 距离其原本位置的距离(越近越富余,越远越贫穷),如果遇到一个已经被占用的 slot,如果它比自己富余,则可以直接替代它的位置,然后把它顺延到新的位置。
(略)
以上介绍的 Hash Tables 要求使用者能够预判所存数据的总量,否则每次数量超过范围时都需要重建 Hash Table。它可能被应用在 Hash Join 的场景下,如下图所示:
由于 A, B 表的大小都知道,我们就可以预判到 Hash Table 的大小。
与 Static Hash Tables 需要预判最终数据量大小的情况不同,Dynamic Hash Tables 可以按需扩容缩容,本节主要介绍 Chained Hashing,Extendible Hashing 和 Linear Hashing。
Chained Hashing 是 Dynamic Hash Tables 的 HelloWorld 实现,每个 key 对应一个链表,每个节点是一个 bucket,装满了就再往后挂一个 bucket。需要写操作时,需要请求 latch。
Extendible Hashing 的基本思路是一边扩容,一边 rehash,如下图所示:
基本思想:维护一个指针,指向下一个将被拆分的 bucket,每当任意一个 bucket 溢出(标准自定,如利用率到达阈值等)时,将指针指向的 bucket 拆分。
slides, video
这么做的好处就是简单,坏处就是最坏的情况下 Hash Table 可能降级成链表,使得操作的时间复杂度降格为 。
Hash Tables 提供 的访问效率,因此它被大量地应用于 DBMS 的内部实现中。即便如此,它并不适合作为 table index 的数据结构,而 table index 的首选就是下节将介绍的 B+ Tree。