数据库存储中的哈希表和B+树
哈希表
哈希表是一种通过哈希函数将键直接映射到存储位置的数据结构,其核心目标是实现平均时间复杂度为 O(1) 的等值查询。
核心原理
哈希函数:接收一个键作为输入,返回一个整数值(哈希值)。一个好的哈希函数应该能将键均匀地分布到整个地址空间。
哈希桶:哈希值对应一个存储单元,通常称为“桶”。一个桶可以包含一个或多个数据项。
解决冲突:当两个不同的键经过哈希函数计算后得到相同的哈希值(即发生哈希冲突)时,需要解决。常见方法有:
链地址法:每个桶维护一个链表,所有映射到该桶的键值对都存储在这个链表中。这是最常用的方法。
开放定址法:当发生冲突时,按照某种探测序列(如线性探测、平方探测)寻找下一个空闲的桶。
在数据库中的应用
哈希表在数据库中最典型的应用是哈希索引。例如,MySQL的Memory存储引擎就支持哈希索引。
存储方式:对于一张表的某一列(如
user_id
)创建哈希索引。数据库会为这一列的值(键)计算哈希值。
根据哈希值找到对应的哈希桶。
将完整的行数据指针 与该键一起存入桶内的链表中。
查询过程:当执行
WHERE user_id = 123
这样的等值查询时:对
123
计算哈希值。定位到对应的哈希桶。
遍历桶内的链表,比较键的值,找到匹配的
user_id = 123
的记录。通过存储的行数据指针,快速读取到完整的行数据。
优点
极高的等值查询效率:在理想情况下(无冲突或冲突很少),一次查询即可定位数据,速度极快。
插入和删除效率高:同样基于哈希定位,插入和删除操作也非常高效。
缺点
无法支持范围查询:这是哈希索引最致命的缺点。由于哈希函数打乱了键的原始顺序,你无法高效地进行
WHERE user_id > 100 AND user_id < 200
这样的查询。数据库只能进行全表扫描。无法利用索引进行排序:
ORDER BY
子句无法使用哈希索引,原因同样是数据无序。不支持最左前缀匹配:对于复合索引(多列索引),哈希索引必须使用所有列进行查询才能生效。
性能受数据分布影响大:如果哈希函数选择不当,导致大量数据聚集在少数桶中,会使链表过长,查询效率退化为 O(n)。
适用场景:主要用于等值查询(=, IN)密集型、且几乎没有范围查询和排序需求的场景,例如缓存(如Redis的Hash结构)、数据字典等。
B+树
B+树是B树的一种变体,它是为磁盘或其他直接存取的辅助存储设备而设计的一种平衡查找树。目前几乎所有关系型数据库(如MySQL的InnoDB、PostgreSQL)的主键索引都采用B+树。
核心特性与结构
一棵B+树具有以下关键特征:
多路平衡查找树:一个节点可以有多个子节点(远多于二叉树的2个),这大大降低了树的高度,从而减少了磁盘I/O次数。
所有数据都存储在叶子节点:这是与B树的主要区别。内部节点(非叶子节点)只充当导航作用,存放键和指向子节点的指针。
叶子节点通过指针顺序链接:所有叶子节点被一个双向链表串联起来,这使得范围查询变得异常高效。
节点大小通常设置为磁盘页的大小(如4KB, 16KB):这样一次磁盘I/O就可以读取一个完整的节点,极大提升了效率。
一个典型的B+树结构如下:
根节点:树的顶端。
内部节点:介于根节点和叶子节点之间。
叶子节点:树的最底层,存储了所有的键值对。其中,键是索引列的值,而“值”根据索引类型不同而不同:
在主键索引中:值就是完整的行数据(InnoDB的聚簇索引)。
在非主键索引中:值是对应主键的值。
在数据库中的应用
以InnoDB引擎为例:
查询过程:当执行
WHERE id = 123
时,从根节点开始,通过节点内的键值进行二分查找,找到下一个子节点,直到最终在叶子节点找到目标记录。范围查询过程:当执行
WHERE id > 100 AND id < 200
时,首先通过一次查找定位到id=100
所在的叶子节点,然后利用叶子节点间的顺序指针,向后顺序遍历即可,无需回溯到上层节点。
优点
出色的范围查询和排序性能:得益于叶子节点的顺序链表。
稳定的查询性能:作为一棵平衡树,所有查询操作的时间复杂度都是 O(log n),非常稳定,不会出现性能突降。
支持最左前缀匹配:对于复合索引(如
index (last_name, first_name)
),可以高效查询WHERE last_name = ‘Smith’
。数据全量遍历高效:只需要遍历叶子节点的链表即可,无需遍历整棵树。
缺点
相对于哈希表,等值查询稍慢:平均需要 O(log n) 次磁盘I/O,而哈希表在理想情况下只需 O(1) 次。
结构更复杂:插入和删除操作可能涉及节点的分裂与合并,以维持平衡。
适用场景:B+树是通用型的索引结构,适用于绝大多数的数据库操作,包括等值查询、范围查询、排序、分组等。是关系型数据库的默认和标准选择。
一个形象的比喻
哈希表就像一本电话簿的“姓名索引”:如果你知道要找“张三”,你可以直接翻到“Z”开头的部分快速查找。但如果你想找“所有姓张的人”,用这种方法就很麻烦,需要把“张”开头的名字都看一遍。
B+树就像一本完整的、按字母顺序排列的电话簿:找“张三”需要先按字母顺序找到“Z”,再找“Zhang”,最后找到“张三”,比直接翻到“Z”部分稍慢一点。但如果你想找“所有姓张的人”,你只需要找到“张”开始的页面,然后一页一页顺序翻下去即可,效率极高。
结论
在数据库存储中,B+树是绝对的主流,因为它完美地契合了磁盘的存取特性和数据库常见的查询模式(混合着等值查询和范围查询)。而哈希表则是一种在特定场景下性能极佳的补充。