引言
数据库存储引擎是数据库系统的核心组件,负责数据的存储、检索和管理。而在数据引擎中,B+树和LSM-Tree(Log-Structured Merge Tree)是两种主流的存储引擎结构,我们一起来看看他们分别有什么优劣势。
存储引擎基本结构比较
B+树结构
B+树是一种多路搜索树,被广泛应用于传统关系型数据库如MySQL的InnoDB等存储引擎中。
B+树特点
-
结构特点:
- 所有数据记录存储在叶子节点
- 内部节点只存储键值和指针,不存储实际数据
- 叶子节点之间通过指针连接,形成有序链表
-
基本操作:
- 查找:从根节点开始,通过比较键值在内部节点间导航,直到找到目标叶子节点
- 插入:找到合适位置插入,可能触发节点分裂
- 删除:找到并删除目标数据,可能触发节点合并
-
存储特性:
- 数据按页组织,一个节点通常对应一个磁盘页
- 支持就地更新(in-place update)
- 磁盘随机读写频繁
LSM-Tree结构
LSM-Tree是一种针对写操作优化的数据结构,代表实现包括RocksDB、LevelDB、HBase等。
LSM-Tree特点
-
结构特点:
- 分层存储架构
- 内存中的MemTable与磁盘上的多层SST文件组成
- 采用日志结构的追加写入方式
-
基本操作:
- 写入:先写WAL日志,再写入内存中的MemTable
- 读取:从MemTable到各层SST文件依次查找
- 后台合并:通过Compaction过程合并文件,整理数据
-
存储特性:
-
- 数据以追加方式写入,避免随机写
- 更新通过写入新记录实现(非就地更新)
- 删除通过添加墓碑记录(tombstone)标记
B+树与LSM-Tree性能对比
写入性能比较
B+树写入特性
优势:
- 单次写入开销可预测且稳定
- 事务支持简单,ACID特性容易实现
- 无需后台合并操作
劣势:
- 随机I/O,写入性能受限于磁盘寻道时间
- 需要同时更新数据和索引页
- 写放大严重:一次逻辑写入可能引起多次物理写入
LSM-Tree写入特性
优势:
- 顺序写入,性能高
- 批量写入效率极高
- 写入不受磁盘寻道时间影响
- 适合高并发写入场景
劣势:
- 需要写两次(WAL + MemTable)
- 后台Compaction过程可能影响前台操作
- 写放大:多次Compaction导致同一数据多次写入
- 峰值写入可能受Compaction影响出现延迟波动
读取性能比较
B+树读取特性
优势:
- 稳定的读取性能,读路径长度固定
- 范围查询高效(叶节点链表支持顺序扫描)
- 热点数据缓存友好
- 无需处理多版本数据
劣势:
- 树高增加会影响随机读性能
- 空间局部性不如LSM-Tree的分层设计
LSM-Tree读取特性
优势:
- 热点数据在内存中,读取极快
- 布隆过滤器有效减少不必要的磁盘访问
- 分层设计使得大部分数据位于高层,读路径短
劣势:
- 最坏情况下需要查询多个层级
- 数据可能分散在多个文件中,增加读放大
- 合并过程可能临时影响读性能
- 复杂的读取路径增加了实现复杂度
空间利用率比较
B+树空间特性
优势:
- 结构紧凑,元数据开销小
- 无需额外的过滤器和索引结构
劣势:
- 删除数据后的空间回收效率低
- 碎片化问题明显
LSM-Tree空间特性
优势:
- 高压缩率,支持多种压缩算法
- 文件紧凑存储,无预留空间
- Compaction过程可回收空间并减少碎片
劣势:
- 多版本数据占用额外空间
- 布隆过滤器等辅助结构占用额外空间
- 合并过程临时需要额外空间
RocksDB详解:LSM-Tree的工程实现
基本架构
RocksDB 是 Facebook 基于 Google 的 LevelDB 开发的一个高性能嵌入式键值存储引擎,其主要组件包括:
- MemTable:内存中的数据结构,最新写入的数据首先存储在这里
- Immutable MemTable:只读的 MemTable,等待被刷入磁盘
- WAL (Write Ahead Log) :预写日志,用于崩溃恢复
- SST (Sorted String Table) 文件:磁盘上的数据文件,按层级组织
- Compaction:压缩过程,合并不同层级的 SST 文件
数据存储流程
写入流程
- 预写日志 (WAL)
-
- 当写请求到达时,RocksDB 首先将操作记录到 WAL 中
- WAL 是一个顺序写入的日志文件,用于崩溃恢复
- 每个写操作都会被追加到 WAL 的末尾
- MemTable 写入
-
- 写入 WAL 后,数据被插入到活跃的 MemTable 中
- MemTable 通常使用跳表 (SkipList) 实现,支持高效的查找和插入
- 键值对在 MemTable 中按键排序存储
-
MemTable 刷盘
- 当 MemTable 达到大小阈值时,它被标记为不可变 (Immutable MemTable)
- 创建新的 MemTable 用于接收新的写入请求
- 后台线程将 Immutable MemTable 的内容刷入磁盘,生成 Level-0 的 SST 文件
- 刷盘完成后,相应的 WAL 可以被安全删除
SST 文件结构
SST 文件是 RocksDB 在磁盘上存储数据的主要形式,其结构如下:
- 文件头:包含元数据信息
- 数据块 (Data Blocks) :存储实际的键值对,按键排序
- 元数据块 (Meta Blocks) :存储额外信息,如 Bloom Filter
- 索引块 (Index Blocks) :指向数据块的索引
- 元索引块 (Meta Index Block) :指向元数据块的索引
- 页脚 (Footer) :包含固定大小的信息,如魔术数字和指向索引块的指针
分层存储与压缩
RocksDB 采用分层存储模型:
-
Level-0:直接由 MemTable 刷盘生成,文件之间可能有重叠的键范围
-
Level-1 及以上:每一层的文件不会有重叠的键范围,容量随层数增加
- Level-L 的大小通常是 Level-(L-1) 的 10 倍
- 键范围不重叠确保了在高层级的读取操作效率
压缩是后台持续进行的过程,用于合并 SST 文件并清理过期数据:
- 触发条件:
-
- Level-0 文件数量超过阈值
- Level-L 的大小超过目标大小
-
压缩策略:
- Leveled Compaction:默认策略,平衡读写性能
- Universal Compaction:优化写入性能,但可能增加读取延迟
- FIFO Compaction:简单地删除旧文件,适用于缓存场景
数据读取流程
查找过程
当需要查找一个键时,RocksDB 按照以下顺序进行查找:
-
内存查找
- 首先在活跃的 MemTable 中查找
- 如果没有找到,继续在所有 Immutable MemTable 中查找(从新到旧)
-
文件查找
- 如果内存中没有找到,开始在 SST 文件中查找
- 先在 Level-0 文件中查找(从新到旧)
- 继续在 Level-1 及以上层级查找(每层最多只需查找一个文件)
-
布隆过滤器优化
- 在查找 SST 文件前,使用布隆过滤器快速确定键是否可能存在
- 如果布隆过滤器表明键不存在,可以直接跳过该文件
读取优化技术
RocksDB 采用多种策略优化读取性能:
1. 缓存机制
块缓存 (Block Cache)
- 工作原理:缓存热点数据块,避免重复磁盘I/O
- LRU 策略:默认采用LRU缓存替换策略
- 分片设计:缓存内部分为多个分片,减少锁竞争
- 优先级分区:支持高低优先级区域,确保关键数据常驻内存
表缓存与行缓存
- 表缓存:缓存打开的SST文件句柄,减少文件打开/关闭操作
- 行缓存:直接缓存键值对,绕过数据块解析和解压缩
2. 索引与过滤器优化
布隆过滤器
- 原理:概率数据结构,快速判断键是否可能存在
- 应用:每个SST文件维护一个布隆过滤器,大幅减少不必要的磁盘I/O
- 优化:支持全键过滤和前缀过滤,可配置常驻内存
分区索引和过滤器
- 优势:将索引和过滤器分割成多个小块,仅加载需要的部分
- 效果:大幅减少内存使用,特别适合大文件
3. 数据布局与压缩优化
- 键值分离:将键和值分开存储,仅在必要时加载值
- 前缀压缩:利用相邻键的共同前缀进行压缩存储
- 块压缩算法:支持Snappy、LZ4、Zlib、ZSTD等多种压缩算法
- 分层压缩策略:不同层级使用不同压缩算法
核心数据结构
MemTable实现
MemTable默认使用跳表(SkipList)实现:
- 跳表是一种概率数据结构,提供O(log N)的查找和插入性能
- 键值对按键的字典序排序存储
- 支持并发访问的无锁设计
- 每个条目包含键、值和序列号
版本控制
RocksDB使用版本控制机制管理文件:
- Version:表示数据库的一个快照,包含每层的文件列表
- VersionSet:管理所有版本,维护当前版本和引用计数
- VersionEdit:表示版本之间的变化,如添加或删除文件
- MANIFEST:记录所有VersionEdit,用于崩溃后恢复
存储引擎优化技术
读取优化技术
无论是B+树还是LSM-Tree,都采用了多种技术优化读取性能:
-
缓存优化
- 多级缓存设计
- 预取机制
- 缓存淘汰策略优化
-
索引优化
- 二级索引
- 前缀索引
- 稀疏索引
-
并行查询
-
- 多线程并行读取
- 分片查询
写入优化技术
- 批量写入
-
- 合并多个小写入为大批量
- 异步写入
- 写缓冲管理
-
- 自适应大小调整
- 多缓冲区设计
- 日志优化
-
- 组提交
- 日志压缩
通用优化技术
-
数据压缩
- 行压缩
- 页压缩
- 字典压缩
-
硬件优化
- SSD优化
- NVME优化
- SIMD指令加速
应用场景分析
B+树适用场景详解
- OLTP数据库
-
- MySQL、PostgreSQL等传统关系型数据库
- 特点:事务处理频繁,单条记录查询和更新
LSM-Tree适用场景详解
-
大规模分布式存储
- Bigtable、HBase、Cassandra
- 特点:可水平扩展,高写入吞吐
-
时间序列数据库
- InfluxDB、Prometheus存储层 - 特点:高频写入,数据按时间顺序追加,很少更新
-
区块链存储
- 特点:数据只追加,不修改,需要高效检索
-
日志分析系统
- 特点:大量写入,批量查询
- 缓存系统
- 特点:需要持久化的缓存,访问模式倾向于热点
结论
B+树和LSM-Tree代表了数据库存储引擎设计的两种主要思路,各有优劣:
- B+树优势在于稳定的读取性能和简单的实现,适合读取为主的OLTP系统。
- LSM-Tree优势在于出色的写入性能和空间效率,适合写密集型和大数据量场景。