真实数据库内部是如何工作的?
Source: Dev.to
请提供您希望翻译的正文内容,我会按照要求将其翻译成简体中文并保留原有的格式。
真正的数据库不是这样
- 一个巨大的内存表
- 一堆 CSV 文件的集合
- 一个简单的键值映射
核心组件
- 解析器
- 计划器 / 优化器
- 执行引擎
- 存储引擎
- 缓冲缓存
- 事务管理器
- 恢复系统
查询处理流水线
当你执行类似下面的语句时:
SELECT name FROM users WHERE age > 30;
数据库并不会立即扫描表。它遵循严格的流水线:
解析与校验
- 将 SQL 文本转换为 抽象语法树 (AST)。
- 检查:
- 语法是否正确
- 表名和列名是否有效
- 用户权限
此阶段不访问任何数据。
优化
查询优化器决定:
- 使用哪些索引
- 连接顺序
- 扫描方式(索引扫描 vs. 顺序扫描)
- 基于统计信息的成本估算
常见的决策:
- 顺序扫描 100 万行是否更便宜?
- 还是使用随机 I/O 的索引更划算?
现代数据库采用:
- 基于成本的优化器
- 统计信息(直方图、基数)
- 基于规则的重写
这一步往往决定了性能,而不是硬件。
执行
优化器生成 执行计划。执行引擎随后:
- 通过运算符(扫描 → 过滤 → 投影)拉取行
- 使用迭代器或向量化执行
- 流式输出结果,而不是一次性加载到内存
重要概念: 数据库以流水线方式处理数据,而不是一次性全部处理。
存储布局
页面
数据以固定大小的页面存储(通常为 4 KB–16 KB)。
每个页面包含:
- 页面头部
- 行槽
- 元数据
页面是 I/O 的最小单位。
行式存储 vs. 列式存储
| 存储类型 | 典型用途 | 特点 |
|---|---|---|
| 行式(PostgreSQL,MySQL) | OLTP | 插入快速且点查询快 |
| 列式(ClickHouse,Redshift) | Analytics | 压缩优秀,向量化扫描 |
索引
索引是独立的数据结构,最常见的有:
- B‑树 – 保持数据有序,最小化磁盘寻道,平衡读/写成本
- 哈希索引
- LSM 树(RocksDB,Cassandra)
重要事实: 索引加快读取速度,但会减慢写入,因为每次插入/更新都必须同时修改表数据和所有相关索引。
Buffer Cache and Durability
数据库从不只信任内存。经常使用的页面会被缓存到 RAM 中,而 dirty pages 则会在以后使用替换策略(LRU 变体)写回磁盘。
如果电源失效,内存会丢失,因此磁盘必须保持一致性。
Source: …
ACID 保证
真实的数据库通过 Write‑Ahead Log (WAL) 来确保 Atomicity, Consistency, Isolation, Durability:
- 在修改数据之前,先将更改写入日志。
- 将日志刷新到磁盘。
- 然后才更新内存中的页面。
如果数据库崩溃,会重新播放 WAL 以恢复数据。日志往往比数据文件本身更为关键。
并发控制
数据库通过以下方式支持成千上万的并发用户:
- 锁(行、页、表)
- MVCC(多版本并发控制)
使用 MVCC 时:
- 读取者不会阻塞写入者。
- 写入者会创建新版本。
- 旧版本由真空/垃圾回收清理。
这就是像 PostgreSQL 这样的系统能够在不加锁的情况下读取,并在负载下良好扩展的原因。
恢复过程
- 读取最近的检查点。
- 重放 WAL 记录。
- 撤销未完成的事务。
- 恢复一致性。
该过程是确定性的且可重复的——无需猜测或启发式方法。
为什么理解内部工作原理很重要
- 编写更快的查询
- 设计更好的模式
- 选择正确的索引
- 避免危险的假设
- 调试性能问题
大多数“慢数据库”问题源于:
- 糟糕的查询计划
- 错误的索引
- 对内部机制的误解
…而不是硬件。
结论
真实的数据库更像操作系统,而不是一个简单的库。它管理内存、存储、并发、恢复和调度。把它当作黑盒使用只会让你受罚;了解其内部机制则能把它变成软件工程中最强大的工具之一。