B树与哈希表

Posted

技术标签:

【中文标题】B树与哈希表【英文标题】:B-Tree vs Hash Table 【发布时间】:2011-11-10 12:03:39 【问题描述】:

mysql 中,索引类型是 b-tree,访问 b-tree 中的元素是以对数摊销时间O(log(n))

另一方面,访问哈希表中的元素是在O(1)

为什么不使用哈希表代替 b 树来访问数据库中的数据?

【问题讨论】:

哈希表不支持范围查询,操作过程中不能平滑增长或收缩。 @HenningMakholm 为什么不对不需要范围查询的列进行哈希处理? 【参考方案1】:

您只能通过哈希表中的主键访问元素。 这比使用树算法(O(1) 而不是 log(n))要快,但您不能选择范围(xy 之间的所有内容) . 树算法在Log(n) 中支持这一点,而哈希索引可以导致全表扫描O(n)。 此外,哈希索引的恒定开销通常更大(这不是 theta 表示法的因素,但它仍然存在)。 此外,树算法通常更易于维护、随着数据、规模等增长。

哈希索引与预定义的哈希大小一起使用,因此您最终会得到一些存储对象的“桶”。这些对象会再次循环以在此分区中真正找到正确的对象。

因此,如果您的尺寸较小,则小元素的开销会很大,而大尺寸会导致进一步扫描。

如今的哈希表算法通常可以扩展,但扩展可能效率低下。

确实有可扩展的散列算法。不要问我这是如何工作的——这对我来说也是一个谜。 AFAIK 他们是从可扩展的复制演变而来的,在这种复制中重新散列并不容易。

它叫做 RUSH - R复制Under Scalable Hashing ,因此这些算法被称为 RUSH 算法。

但是,与您的哈希大小相比,您的索引可能会超过一个可容忍的大小,并且您的整个索引需要重新构建。通常这不是问题,但对于庞大的数据库,这可能需要几天时间。

树算法的权衡很小,它们几乎适用于所有用例,因此是默认的。

但是,如果您有一个非常精确的用例,并且您确切知道需要什么并且只需要什么,则可以利用散列索引。

【讨论】:

您能详细解释一下索引重建吗?这是否意味着在索引重建的 x 天内,该表在此期间完全无法使用? 这取决于使用的数据库系统。这个问题只涉及理论方面。我不太了解常见数据库系统的实现细节。但通常情况并非如此,因为可以在第一个索引仍在使用时构建第二个索引 “您只能通过主键访问元素” - 您的意思是指具有索引权的列的值,无论是主键还是其他类型的索引? 您如何看待 LSM-Trees?他们使用 SSTables(排序字符串表),它们是按键排序的数据段(文件)(这要归功于内存中的 memtable,它本质上是一个 AVL 树,当达到数据阈值时,它会定期清空并写入磁盘 -通常为几 MB)并使用内存中的哈希映射来有效地检索段中的数据。据我所知,这种数据索引还允许有效的范围查询。【参考方案2】:

实际上,根据以下link,MySQL 似乎使用哈希表或 b-tree 两种索引。

使用 b 树和哈希表的区别在于前者允许您在使用 =、>、>=、仅用于使用 = 或 运算符的相等比较。

【讨论】:

那不公平。最佳答案得分最低。 这正是我想要的。我关心的是它如何影响我的查询,而不是技术分析。 是的!这个答案对我帮助最大。 非常感谢,已经很久了,但这个答案对我也有很大帮助。 唯一有意义的答案,你总是可以在哈希表键中实现一个列表,开销与 b-trees 没有什么不同,只是 b-trees 在事情。此外,无需即时重建哈希表,您只需制作更多哈希表(一点一点地增加总寻道时间)并离线重建。这里的主要考虑因素是哈希表需要提前进行更多规划,但如果对它们进行足够的思考,IMO 会取得更好的结果。【参考方案3】:

哈希表的时间复杂度仅对于足够大的哈希表是恒定的(需要有足够的桶来保存数据)。事先不知道数据库表的大小,因此必须不时重新散列该表以从散列表中获得最佳性能。重新散列也很昂贵。

【讨论】:

可以在 db 在线时执行 reshashing 吗?还是我们必须锁定表才能重新散列所有内容? Pacerier,MySQL 不支持哈希索引。理论上可以在数据库仍然在线时重新哈希索引(继续使用旧索引,创建一个新索引,完成后切换到新索引)但我不知道如果他们实现 MySQL 会做什么哈希指数。 MySQL 支持哈希索引对吗? :dev.mysql.com/doc/refman/5.5/en/index-btree-hash.html 你是对的。我的回答实际上是错误的。如果我今天回答这个问题,我会在this answer for MongoDB 中说类似的话,我在其中激发了为什么 b 树在实践中具有有效的 O(1) 查找。 @EmilVikström - MongoDB 的参数适用于 MySQL,但使用了大约 log_100。 (InnoDB 扇出的经验法则是 100;十亿行需要 5 个级别。)【参考方案4】:

我认为 Hashmap 的扩展性不好,而且当整个地图需要重新散列时可能会很昂贵。

【讨论】:

【参考方案5】: MySQL 仅在几种情况下支持 HASH:ENGINE=MEMORY(很少使用)和 内部 用于“hash-join”。 即使您要求 InnoDB 表具有 HASH 索引,它也会默默地将其转换为 BTree。 哈希接近 O(1),但从技术上讲,它更像是最坏情况下的 O(N^2)。这是因为需要处理“碰撞”。 MySQL 选择 BTree 是因为它比 Hash 更灵活(因为它可以处理范围),同时又不比 Hash 慢很多。 可以说,由于块的缓存,BTree 的速度慢到 O(1)。非叶节点往往会被缓存并保留在 RAM 中,即使叶节点来来去去(对于大型表)也是如此。 MySQL 动态维护 BTree;虽然您可以要求重建索引(参见 OPTIMIZE),但几乎不值得付出努力。 在 InnoDB 中。数据存储在由PRIMARY KEY 排序的BTree 中。辅助键也存储在单独的 BTree 中,但按辅助键列排序。叶节点中唯一的其他信息是PRIMARY KEY 值。因此,次键查找需要两次 BTree 查找(除非所有必要的列都在次要列+主列中——这称为“覆盖”)。

我最后说 Big-O 可能很有趣,但实现的细节增加了复杂性。以及任意大表的性能。

【讨论】:

【参考方案6】:

除了这里的好答案,这里是思考如何构建数据库时的一些观点。

首先,健壮 哈希表通常使用分桶系统完成,例如在 Quadratic Probing 中,它用于实现 javascript“对象”(即哈希表)。您可以在 JavaScript here 中看到分桶哈希表的实现。

您会注意到,在这个实现中,进行的处理比O(1) 表示法看到的要多得多。首先,您通过散列函数运行它,该函数迭代输入字符串的长度,并且每次迭代有 5+ 个计算步骤。但请注意,这些都是快速计算步骤,因为它们都是在寄存器中完成的,而不是在 RAM 中。接下来,您使用该哈希值来获取 bucket。我不确定有多少个桶,或者一个桶有多长,但桶是一个数组或链表。因此,您遍历存储桶项目,并将每个项目与您要为其获取值的输入键进行比较。这又是一个字符串比较。因此,我很可能估计即使是一个简单的字符串也至少需要 100 个计算步骤才能从哈希表中获取它。所有这些字符串比较加起来。

此外,桶可能是半空的,这会占用大量无用的空间。最后,当哈希表的占用量达到一定大小时,它的大小必须翻倍!它必须重新处理和重新计算所有内容。这可能会导致 UI 应用程序出现明显故障。

另一方面,B+树是一种更紧凑的数据结构。您仍在进行字符串比较,但您只跳跃 MAX 我会说树中的 20 个链接(就深度而言),然后扫描最后一个树节点中的子节点以找到精确匹配。

从这个意义上说,我认为实际上 B+tree 或 B-tree 的性能将与哈希表相当,尤其是幼稚的实现。两个系统都可以优化和微调,我仍然认为它们将接近相等。只有测试会告诉我们。但是树的优点是在内存方面更紧凑。因此,在考虑了很长时间并权衡了方程式的各个方面之后,我将选择 B+树作为快速按键查找项目的理想解决方案。

【讨论】:

【参考方案7】:

Pick DB/OS 基于散列并且运行良好。现在有更多的内存来支持高效的稀疏哈希表,以及冗余哈希来支持适度的范围查询,我想说哈希可能还有它的位置(有些人宁愿有其他形式的非范围相似性匹配,例如通配符和正则表达式)。我们还建议在内存层次结构的速度差异很大时进行复制以保持碰撞链的连续性。

【讨论】:

【参考方案8】:

另一件可能会影响选择的事情:哈希表可以很好地将键映射到一个单一的值。但是,在一个键映射到大量元素的情况下(对于表的单列非常常见),您很容易失去 O(1) 行为,具体取决于它如何处理它。 BTrees 没有这个问题,并且可以出色地处理大量重复条目。

【讨论】:

创建一个始终映射到完全不同的值的哈希函数几乎是不可能的。用于索引目的的散列并不担心这一点。也就是说,在任何 Hash 实现中都可能发生一些冲突。因此“通常 O(1)”。 InnoDB 的PRIMARY KEY BTree 必须没有重复项(PK 是唯一的)。二级索引隐含包含 PK,因此它们也没有重复。

以上是关于B树与哈希表的主要内容,如果未能解决你的问题,请参考以下文章

插入带位置的有序哈希表

蓝书《哈希与哈希表》——知识整理

哈希表/散列表

哈希表

字典和哈希表空间复杂度

数据结构 哈希表建立