手写数据库内核!从零实现ToyDB第一课:全景架构与核心概念
导语:数据库是每个开发者绕不开的基石,但你真正理解它内部的运作奥秘吗?本文作者将带你从零开始编写一个关系型数据库------ToyDB,并公开完整架构设计与核心代码。读完本文,你将彻底明白SQL如何变成磁盘上的0和1。
一、为什么要自己写一个数据库?
"数据库嘛,会用SELECT * FROM不就行了?"------如果你也这么想,那么当面试官问到索引为何用B+树 、事务隔离级别如何实现 、崩溃恢复原理时,只能尴尬地搓手手。
市面上的数据库教材汗牛充栋,但大多停留在理论层面。为了真正把知识焊死在脑子里 ,我决定用Python 手撸一个关系型数据库,取名ToyDB,并将全过程写成系列文章,从存储引擎到分布式扩展,一个模块都不放过。
📌 为什么选择Python?
Python的简洁语法让我们能用最少代码表达最核心的数据库算法。ToyDB是一份可执行的架构设计文档。当你理解了B+树的分裂过程、MVCC的版本链之后,再去看C++或Rust的实现,将势如破竹。
今天先送上第一章------数据库系统全景概览,以及ToyDB的整体架构设计。
二、数据库进化简史:从打孔纸带到云原生
我们先花3分钟回顾一下数据库的"前世今生",这有助于理解现代数据库为何长成今天这个样子。
1950s 前数据库时代 手工管理,文件系统萌芽 1960s 文件系统阶段 层次数据库(IMS) 网状数据库(CODASYL) 1970s 关系数据库革命 Codd提出关系模型 System R, Ingres诞生 1980s 关系数据库商用化 Oracle, DB2, SQL Server 1990s 对象关系数据库 PostgreSQL, Informix 2000s NoSQL运动 MongoDB, Cassandra, Redis 2010s NewSQL与云原生 Spanner, TiDB, CockroachDB 2020s 智能自治数据库 存算分离, Serverless DB 数据库系统演进历程
| 时代 | 代表技术 | 核心痛点 |
|---|---|---|
| 前数据库时代(1950s前) | 手工管理、磁带 | 数据与程序绑定,改动数据结构=重写全部代码 |
| 文件系统阶段(1950-60s) | 磁盘文件 | 数据冗余、不一致、并发访问困难 |
| 层次/网状数据库(1960s) | IMS、CODASYL | 查询复杂,必须按指针路径导航 |
| 关系数据库革命(1970s) | SQL、System R | 用数学(关系代数)抽象数据,至今统治世界 |
| 现代数据库(1980s-今) | NoSQL、NewSQL、云数据库 | 应对互联网规模的海量数据与高并发 |
💡 灵魂拷问 :关系模型凭什么赢了?
答:数据结构简单(二维表)、物理与逻辑独立、SQL声明式查询、完善的事务理论。
三、现代数据库内部长什么样?
别看数据库对外就是一个黑盒,内部却是一个精密的分层架构。下面这张图建议保存,几乎适用于所有主流关系型数据库(Oracle、MySQL、PostgreSQL均如此)。
持久化层
数据文件
日志文件
配置文件
存储引擎层
缓冲区管理器
索引管理器
磁盘管理器
事务管理层
事务管理器
并发控制
恢复管理器
查询处理层
查询解析器
查询优化器
执行引擎
应用程序层
应用工具
客户端应用
各层职责速览:
- 查询处理器:将SQL字符串变成高效的执行计划。
- 事务管理器:保证ACID,让你转账不会丢钱。
- 存储引擎:管理数据在内存和磁盘上的布局,是性能的关键。
一条SELECT语句的旅程
磁盘 缓冲池 事务管理器 执行引擎 查询优化器 SQL解析器 客户端 磁盘 缓冲池 事务管理器 执行引擎 查询优化器 SQL解析器 客户端 发送SQL语句 生成AST,转为逻辑计划 生成最优物理执行计划 申请事务ID,获取读锁 返回事务上下文 请求数据页(读操作) 若缺页,从磁盘读取 返回数据页 返回页内元组 返回查询结果 提交事务,释放锁
四、关系型数据库核心概念速成
在正式编码前,我们必须夯实理论基础。关系模型三大要素:
4.1 关系模型基本要素
| 术语 | 解释 | 类比 |
|---|---|---|
| 关系(表) | 由行和列组成的二维结构 | Excel工作表 |
| 属性(列) | 描述实体的特征 | 表格的列头 |
| 元组(行) | 关系中的一个具体实例 | 表格的一行数据 |
| 域 | 属性的取值范围 | 该列的数据类型和约束 |
4.2 键与约束
- 超键:能唯一标识元组的属性集合(可能有冗余)
- 候选键:最小超键,没有冗余属性
- 主键:被选为主要标识符的候选键
- 外键:建立表之间关系的属性
python
# 关系模型中的键约束示例(简化版)
class Table:
def __init__(self, name, columns, primary_key=None):
self.name = name
self.columns = columns
self.primary_key = primary_key
self.rows = []
def insert_row(self, row_data):
# 主键唯一性检查
if self.primary_key:
pk_value = row_data[self.primary_key]
for row in self.rows:
if row[self.primary_key] == pk_value:
raise ValueError("主键冲突!")
self.rows.append(row_data.copy())
4.3 关系代数------SQL的数学根基
| 操作 | 符号 | SQL对应 | 说明 |
|---|---|---|---|
| 选择 | σ | WHERE | 筛选符合条件的行 |
| 投影 | π | SELECT 列名 | 选择特定列 |
| 并 | ∪ | UNION | 合并两个查询结果 |
| 差 | - | EXCEPT | 找出在一个结果集中但不在另一个中的行 |
| 笛卡尔积 | × | FROM 表1, 表2 | 所有可能的组合 |
| 连接 | ⋈ | JOIN | 根据条件组合两个关系的元组 |
理解这层关系,再复杂的SQL都能拆解为基本的关系代数操作。
4.4 规范化理论------为什么要拆表?
规范化的本质是减少数据冗余、避免更新异常 。记住一个口诀:一个表只描述一件事。
| 范式 | 核心要求 | 解决的问题 |
|---|---|---|
| 1NF | 属性原子性(列不可再分) | 基础结构化 |
| 2NF | 消除部分函数依赖 | 非主键列必须完全依赖主键 |
| 3NF | 消除传递函数依赖 | 非主键列不能依赖其他非主键列 |
| BCNF | 消除主键内的传递依赖 | 更严格的3NF |
五、ToyDB架构揭秘:麻雀虽小,五脏俱全
ToyDB严格遵循上述分层设计,代码结构清晰到可以直接用作教学案例。
5.1 系统架构图
ToyDB系统架构
Client API
Network Layer
TCP Server
SQL Parser
PLY词法/语法分析
Query Optimizer
规则优化+成本优化
Execution Engine
火山模型
Transaction Manager
ACID保障
Storage Engine
页式存储
Buffer Manager
LRU-K缓存
Disk Manager
文件读写
Lock Manager
S2PL+死锁检测
WAL/Logger
崩溃恢复
Index: B+Tree
磁盘页内实现
5.2 模块职责一览
| 模块 | 核心职责 | 关键数据结构 |
|---|---|---|
| Disk Manager | 磁盘空间分配与回收,文件读写 | PageId, FileHandle |
| Buffer Manager | 内存页缓存,LRU-K替换策略 | Frame, PageTable |
| Page | 固定大小数据页(4KB),支持tuple存储 | PageHeader, SlotArray |
| SQL Parser | 词法/语法分析,生成AST | ASTNode, SelectStmt |
| Optimizer | 基于规则的优化(谓词下推等) | LogicalPlan, PhysicalPlan |
| Executor | 火山模型迭代器 | SeqScan, Filter, Project |
| Lock Manager | 严格两阶段锁,死锁检测 | LockTable, WaitGraph |
| WAL | 预写日志,保证持久性 | LogRecord, Checkpoint |
| B+Tree | 磁盘页内实现,支持分裂合并 | BTreePage, IndexKey |
5.3 页式存储结构预览(第二章会详细展开)
Page
+page_id: int
+lsn: int
+checksum: int
+free_space_offset: int
+slot_count: int
+get_tuple(slot_id)
+insert_tuple(data)
+delete_tuple(slot_id)
PageHeader
+page_id
+lsn
+checksum
+free_space_offset
+slot_count
SlotArray
+slots: List<Slot>
+get_offset(slot_id)
TupleData
+data: bytes
六、动手环节:搭建ToyDB开发环境
书中第一章提供了完整的环境搭建脚本 ,运行即可自动生成项目骨架。我已帮你升级到Python 3.11.9,并配置好必要依赖。
6.1 环境检查与项目初始化
python
# setup_env.py - 一键生成项目结构
import sys
import os
def check_environment():
"""检查开发环境"""
required_version = (3, 11, 9)
if sys.version_info < required_version:
print(f"需要Python {required_version[0]}.{required_version[1]}.{required_version[2]}或更高版本")
return False
try:
import pytest
import numpy
print("✅ 环境检查通过")
return True
except ImportError as e:
print(f"❌ 缺少依赖: {e}")
return False
def setup_project_structure():
"""创建项目目录结构"""
directories = [
'src/storage',
'src/query',
'src/transaction',
'src/index',
'src/metadata',
'src/network',
'src/utils',
'tests/unit',
'tests/integration',
'data',
'docs',
'examples'
]
for directory in directories:
os.makedirs(directory, exist_ok=True)
print(f"📁 创建目录: {directory}")
# 创建主要模块文件(略,完整代码见GitHub)
print("✅ 项目结构设置完成")
if __name__ == "__main__":
if check_environment():
setup_project_structure()
运行脚本后,你将看到如下输出:
text
✅ 环境检查通过
📁 创建目录: data
📁 创建目录: docs
📁 创建目录: examples
📁 创建目录: src/storage
📁 创建目录: src/query
...
✅ 项目结构设置完成
6.2 生成的目录结构
text
ToyDB/
├── main.py # 主程序入口
├── requirements.txt # 项目依赖
├── README.md # 项目说明
├── data/ # 数据存储目录
├── docs/ # 文档目录
├── examples/ # 示例代码
├── src/ # 源代码目录
│ ├── storage/ # 存储引擎
│ ├── query/ # 查询处理
│ ├── transaction/ # 事务管理
│ ├── index/ # 索引实现
│ ├── metadata/ # 元数据管理
│ ├── network/ # 网络层
│ └── utils/ # 工具函数
└── tests/ # 测试目录
├── unit/ # 单元测试
└── integration/ # 集成测试
七、本章小结与思考题
核心要点回顾
- 数据库演进:文件系统 → 层次/网状 → 关系模型 → NoSQL/NewSQL
- 现代数据库分层架构:查询处理 → 事务管理 → 存储引擎 → 磁盘
- 关系模型核心:表、键、关系代数、规范化
- ToyDB采用模块化设计,用Python清晰呈现数据库内核原理
思考题
- 关系模型相比层次模型和网状模型有哪些压倒性优势?
- 现代数据库系统的主要组件有哪些?它们如何协同工作?
- 为什么需要数据库规范化?3NF解决了什么问题?
- 不同类型的数据库系统各适用于什么场景?
- 通过实现一个完整的数据库系统,你期望获得哪些方面的提升?
💬 欢迎在评论区留下你的答案,我会逐一点评!
八、下集预告与资源获取
下一章 :我们将深入存储引擎 ,亲手实现磁盘管理器 和缓冲池,见证数据如何在内存与磁盘间优雅流动。
本文涉及的完整第一章源码及项目初始化脚本,已上传至GitHub:
🔗 GitHub仓库 :ToyDB - 从零实现的数据库系统
📦 具备CRUD代码Demo :
ToyDB_Demo
如果觉得本文对你有帮助,请点赞、收藏、转发,你的支持是我持续更新的最大动力!
关注我,第一时间获取后续章节更新。有任何问题,欢迎在评论区交流讨论!
本文为《从零开始编写数据库系统:架构设计与实现》系列第一章,作者:安楠的数智笔记。未经授权,禁止转载。