存储引擎总览:从 Page 到 LSM

数据库的「内脏」绝大多数在存储引擎里。SQL 解析、优化、执行最终都要落到底层数据的组织方式上。这一篇是《数据库内核原理》系列的支柱文章,先建立全局视图。

存储引擎要解决什么问题

一句话:把内存中的数据模型,映射到磁盘上的物理布局。这条映射要同时满足三个目标:

  1. 快:读写延迟低、吞吐高
  2. 稳:崩溃后数据不丢、结构一致
  3. 省:磁盘空间、内存缓冲都要物尽其用

这三个目标天然互相拉扯——没有银弹,只有取舍。存储引擎的流派差异,本质是取舍点的差异。

两个基本流派

页式存储(Page-Oriented)

以固定大小的页(通常 4KB~16KB)为读写单位,代表是 PostgreSQL、MySQL(InnoDB)。

┌──────────────────────────────────────────┐
│             磁盘上的页 (Page)             │
├──────────┬──────────┬──────────┬─────────┤
│ Page 头  │ 槽数组    │ 记录数据  │ 空闲区  │
│ (页号/校验)│ (偏移表) │ (变长行)  │         │
└──────────┴──────────┴──────────┴─────────┘
  • 优点:随机读写友好、索引结构成熟(B+ 树)、事务实现直观
  • 缺点:随机写放大(一次 UPDATE 要读改写整个页)

日志结构合并(LSM-Tree)

以追加写为主,数据先写内存表(MemTable),再批量刷盘为不可变的 SSTable,代表是 RocksDB、LevelDB、Cassandra。

  • 优点:写路径几乎全是顺序 I/O,写入吞吐极高
  • 缺点:读放大与空间放大,需要 Compaction 维持结构

三大基础设施

无论哪个流派,存储引擎都有三件套:

组件 职责 典型实现
缓冲池 Buffer Pool 缓存磁盘页,管理 LRU 淘汰 InnoDB Buffer Pool
日志 Log 记录变更,保证崩溃恢复 WAL(Write-Ahead Log)
索引 Index 加速数据定位 B+ 树、LSM、Hash

三者的关系:先写日志再改数据(WAL),先查缓冲再读磁盘,索引与数据分离(聚簇索引除外)。

行存与列存

  • 行存(Row-Oriented):一行记录连续存放,适合 OLTP 的整行读写
  • 列存(Column-Oriented):一列数据连续存放,适合 OLAP 的大范围聚合扫描
行存:  [id=1,name=A,age=20] [id=2,name=B,age=30]
列存:  id:[1,2]  name:[A,B]  age:[20,30]

分析型查询只读需要的列,列存能把无关数据完全跳过——这就是 ClickHouse 比 MySQL 快几十倍的原因之一。

本系列路线图

接下来的三篇分别深入:

  1. B+ 树索引:磁盘索引为什么是 B+ 树而不是二叉搜索树
  2. LSM-Tree 与 WAL:追加写如何工作,崩溃恢复怎么保证
  3. 查询优化器:SQL 如何变成高效的执行计划

💡 阅读建议: 本系列面向「想理解数据库内部」的工程师,不要求有数据库内核开发经验,但建议至少用过 PostgreSQL 或 MySQL。

小结

存储引擎没有完美的流派,只有适合场景的取舍。理解「页式 vs LSM」「行存 vs 列存」「缓冲 vs 直接 I/O」这些对立面,你就拿到了阅读任何数据库源码的地图。