存储引擎总览:从 Page 到 LSM
数据库的「内脏」绝大多数在存储引擎里。SQL 解析、优化、执行最终都要落到底层数据的组织方式上。这一篇是《数据库内核原理》系列的支柱文章,先建立全局视图。
存储引擎要解决什么问题
一句话:把内存中的数据模型,映射到磁盘上的物理布局。这条映射要同时满足三个目标:
- 快:读写延迟低、吞吐高
- 稳:崩溃后数据不丢、结构一致
- 省:磁盘空间、内存缓冲都要物尽其用
这三个目标天然互相拉扯——没有银弹,只有取舍。存储引擎的流派差异,本质是取舍点的差异。
两个基本流派
页式存储(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 快几十倍的原因之一。
本系列路线图
接下来的三篇分别深入:
- B+ 树索引:磁盘索引为什么是 B+ 树而不是二叉搜索树
- LSM-Tree 与 WAL:追加写如何工作,崩溃恢复怎么保证
- 查询优化器:SQL 如何变成高效的执行计划
💡 阅读建议: 本系列面向「想理解数据库内部」的工程师,不要求有数据库内核开发经验,但建议至少用过 PostgreSQL 或 MySQL。
小结
存储引擎没有完美的流派,只有适合场景的取舍。理解「页式 vs LSM」「行存 vs 列存」「缓冲 vs 直接 I/O」这些对立面,你就拿到了阅读任何数据库源码的地图。