知识卡片

哈希索引配合分段压缩及其无法支持范围查询的局限

结构图卡

内容

[[索引是读写速度的权衡额外结构总会拖慢写入]]最简单的实现方式是哈希索引:维护一个 内存中的哈希映射,把每个键映射到数据文件里对应值的字节偏移量,写入时同步更新这个 映射,读取时先查哈希映射拿到偏移量,再对文件做一次seek读取——Bitcask(Riak默认 存储引擎)就是这样做的。为了避免仅追加写入耗尽磁盘空间,日志被拆成固定大小的段, 写满一个段就开新段,旧段可以在后台做压缩(丢弃同一个键的旧版本,只留最新值),并且 可以顺便把多个段合并成更大的段——因为段一旦写入就不再修改,合并只需要写一个新文件, 合并期间旧段仍可正常提供读写服务,合并完成后再把读请求切到新段、删除旧段。这种设计 天然要求单个写入线程(写入严格按顺序追加)配合多线程读取(不可变段可以被安全并发 读取)。哈希索引最大的两个局限是:哈希映射必须完全放进内存,键的数量一旛超出可用 内存就无法工作(磁盘上的哈希映射需要大量随机I/O,扩容和哈希冲突处理都很昂贵); 以及范围查询效率低下——想查”kitty00000到kitty99999之间的所有键”,只能在哈希映射 里逐个单独查找,完全无法利用”相邻键”这个概念,因为哈希函数本身就是打乱顺序的。

结构图

flowchart TD
    A[写入] --> B[追加到当前日志段文件]
    B --> C[同步更新内存哈希映射: 键→字节偏移量]
    C --> D[段写满后开启新段]
    D --> E[旧段后台压缩: 丢弃重复键, 只留最新值]
    E --> F[多个段合并为更大新段]
    A -.单写入线程, 保证顺序追加.-> A
    F -.段不可变, 可安全被多线程并发读取.-> F
    G[哈希映射必须完全放入内存] -.键量超出内存即失效.-> G
    H[无法高效范围查询] -.哈希打乱了键的顺序.-> H

参考来源

- 位置:《数据密集型应用系统设计》第三章《存储与检索》"哈希索引"(源文件: _epub-src/ch3_split_000.html) - 结论依据:原文详述哈希索引维护内存映射到文件偏移量、Bitcask的实现方式、分段 压缩与合并的后台流程、单写入线程配合多线程读取的并发模型,并明确指出哈希表 必须能放进内存及范围查询效率不高两大局限,直接支撑本卡片的结构梳理。 - 原始内容:最简单的索引策略就是:保留一个内存中的哈希映射,其中每个键都映射到一个 数据文件中的字节偏移量……散列表必须能放进内存……范围查询效率不高……您无法轻松 扫描kitty00000和kitty99999之间的所有键——您必须在散列映射中单独查找每个键。