从关系模型到事务:宾州州立数据库内核精讲,手把手实现数据库

发布时间:2026/9/8 6:52:27
从关系模型到事务:宾州州立数据库内核精讲,手把手实现数据库 这次我们来看一个数据库内核方向非常硬核的学习资源由宾夕法尼亚州立大学团队打造、带有中英双语字幕的数据库实现精讲课程内容从关系模型一路讲到事务定位是“手把手教你打造数据库”。先说结论如果你已经会写 SQL、平时用 MySQL 或 PostgreSQL 做增删改查但一直没搞懂数据库底层到底怎么把数据存下来、一条查询怎么变成执行计划、多个事务并发时靠什么保证一致性这门课就是补这块知识的好选择。它不空谈概念而是带着你用工程方式实现一个可运行的数据库内核同时因为带中英双语字幕英文术语跟中文概念能一次对齐学习门槛比直接啃英文原版课程低不少。这篇文章我会先把课程的核心内容和能力覆盖整理成一个速览表然后按照“关系模型 → 存储引擎 → 查询执行 → 事务”这条主线拆解每个模块学什么、需要掌握哪些核心概念、有哪些可以动手验证的实验方向接着给出推荐的学习路线、需要准备的环境、中英双语学习的重点术语对照以及一套常见问题排查清单。文章最后会给出一个偏个人向的学习优先级建议方便你在有限时间里做出取舍。1. 核心能力速览能力项说明课程来源宾夕法尼亚州立大学Penn State数据库内核相关课程具体以公开视频平台发布信息为准语言视频原声为英文同步提供中文字幕形成中英双语学习体验覆盖范围关系模型、数据库存储、索引、查询执行与优化、事务、并发控制、故障恢复等数据库内核核心模块核心主线从关系模型出发到存储、执行、事务最终落到一个可运行的数据库原型实现题目定位“手把手教你打造数据库”面向数据库工程实现而不是只讲理论适合人群数据结构与算法有一定基础、熟悉 SQL、希望深入数据库底层实现的开发者学习方式视频精讲 子模块拆解 动手实现 中英术语对照前置环境C 开发环境、CMake、SQLite 或测试数据库作为参照具体以课程要求为准是否免费公开平台资源通常以免费形式呈现以实际发布渠道为准工程产出通过跟随课程实现数据库核心存储、索引和事务模块理解真实数据库“从零到一”的构建过程需要说明的是课程的具体课时数、作业脚本、实验平台等细节需要以你实际打开的课程页面为准。上面表格里能确认的信息基本都来自这个项目的标题、摘要和公开定位不额外虚构参数。2. 数据库内核到底在学什么很多人写了三五年 SQL对数据库的了解依然停留在客户端工具层面建表、写查询、看执行计划、优化索引。这些确实够日常开发用了但一旦涉及以下问题你就会发现知识不够用为什么有些查询明明加了索引还是慢为什么高并发下会出现死锁、锁等待、脏读、不可重复读为什么数据库突然宕机后已提交的数据没有丢没提交的却被回滚了一条多表 JOIN 的查询底层到底用什么算法在跑同样是存数据为什么有的数据库用 B 树有的用 LSM Tree这些问题的答案全在数据库内核里。数据库内核通常可以划分为四个层次第一层关系模型与 SQL 语义。这一层决定“数据库能表达什么”。关系模型把数据组织成二维表用关系代数和关系演算为 SQL 提供理论基础。学完这一层你才能理解为什么 SQL 能表达几乎所有业务查询也才能理解视图、子查询、连接这些操作的本质。第二层存储引擎。这一层决定“数据怎么落盘”。它负责把内存里的数据高效地组织成文件、页、索引结构并处理缓冲区、磁盘 IO、并发访问。你常用的 InnoDB、MyISAM、RocksDB本质都是存储引擎。第三层查询执行。这一层决定“一条 SQL 怎么跑”。从 SQL 文本到抽象语法树再到逻辑计划、物理计划最后通过一组算子Scan、Filter、Join、Aggregate执行并返回结果。查询优化器在这一层决定走索引还是全表扫描、用哪种 Join 算法、如何调整算子顺序。第四层事务与恢复。这一层决定“数据库怎么保证不丢数据、不错数据”。事务的 ACID 特性依赖并发控制锁、多版本并发控制和故障恢复日志、检查点、重做与回滚。这也是很多人在应用层搞不清楚的地方。这门课的价值在于它不是把这四层当成孤立知识点来讲而是要求你亲手把它们串起来最终形成一个能响应用户输入的数据库原型。3. 课程主线拆解从关系模型到事务结合公开材料这门课的内容主线可以拆成下面几个阶段3.1 关系模型与 SQL 处理课程从关系模型开始这是所有关系型数据库的地基。你需要建立这几个认知关系是一个带有列名和约束的二维表关系代数包含选择、投影、连接、并、交、差等基本运算SQL 是关系代数的“用户友好封装”。一个非常关键的思维转变是SQL 写出来是什么样不重要数据库怎么理解它才重要。SQL 文本要被解析成语义等价的逻辑查询计划再转换成物理执行计划。举个例子下面这条 SQLSELECT student.name, course.title FROM student JOIN course ON student.course_id course.id WHERE student.score 60它在关系代数层面等价于π(student.name, course.title)( σ(student.score 60)( student ⋈ student.course_id course.id course ) )理解这个等价关系是后续学查询优化的前提。3.2 存储引擎页、缓冲区、索引接下来进入存储引擎。这是数据库中最“工程化”的部分也是最容易在视频里看到实际操作的部分。存储引擎要解决的核心问题是内存有限、磁盘很慢怎样让读写又快又稳答案是一层层缓存和索引数据按页存储一页通常是 4KB 或 8KB。缓冲池Buffer Pool在内存里管理最近访问的页通过替换算法决定哪些页保留、哪些写回磁盘。索引是为了避免全表扫描而设计的额外结构常见的就是 B 树。B 树可以算这门课的一个重点。它跟普通二叉搜索树不同节点能存多个键、树的层数更少、所有数据落在叶子节点、叶子节点之间有指针串联非常适合磁盘这种大块读写场景。课程一般会要求你实现一个简化 B 树支持插入、查找、范围扫描。一个经典的页结构设计思路是这样的constexpr int PAGE_SIZE 4096; struct Page { // 页头 uint32_t page_id; uint32_t num_records; uint32_t free_space_pointer; bool is_dirty; // 页体用字节数组承载实际记录数据 char data[PAGE_SIZE - 64]; };这里有一个从课程中能得到的重要认知数据库里“删一条记录”往往不是立刻抹掉磁盘上的字节而是标记删除或就地更新最终靠后台清理和页重组完成空间回收。这也是为什么数据库表在频繁删除后需要OPTIMIZE TABLE之类的操作来回收空间。3.3 查询执行从 SQL 到结果查询执行阶段你要从“数据结构”切换到“执行引擎”视角。一条 SQL 的旅程大致是词法分析和语法分析生成抽象语法树。绑定Binding把表名、列名映射到具体的 schema 对象。生成逻辑计划也就是一棵由关系代数算子组成的树。应用查询优化规则比如谓词下推、投影裁剪、连接顺序调整。生成物理计划指定每个算子具体用什么算法执行。执行算子树逐行或批量返回结果。在实现层面你会接触到几种不同的执行模型迭代器模型Volcano Model每个算子实现next()一次返回一行。可读性好通用但虚函数调用开销大。物化模型一次计算完整结果。适合批量分析但内存压力大。向量化模型一次返回一批行比如 1024 行同时利用 SIMD 加速。这是现代分析型数据库的主流做法。连接操作是一个考研功底的环节。课程里通常会覆盖三种 Join 算法Nested Loop Join最简单双重循环适合小表连接小表。Hash Join为其中一个表建哈希表另一表逐行探查适合等值连接和大表连接。Sort-Merge Join先按连接键排序再归并匹配适合大表之间按序连接或非等值连接。这里你能直观看到为什么常说“查询优化器选择了错误的 Join 算法性能可能差几个数量级”。3.4 事务ACID、并发控制、故障恢复事务是这门课的重头戏也是数据库内核里最抽象、最难在应用层体会到的一层。课程会把 ACID 拆开来讲原子性事务里的操作要么全部提交要么全部回滚。一致性事务执行前后数据库都满足约束条件。隔离性并发事务之间互相不干扰或少干扰。持久性已提交事务的修改不会因为数据库崩溃而丢失。实现原子性和持久性的常见手段是预写日志WALWrite-Ahead Logging。核心原则是日志先落盘数据再落盘。这样即使数据库崩溃也可以通过日志重做Redo或回滚Undo恢复状态。一个简化版的 WAL 写入流程可以这样理解// 提交事务前保证日志先落盘 void CommitTransaction(TransactionId txn_id, std::vectorLogRecord logs) { // 1. 将本次事务产生的所有日志记录写入日志缓冲区 for (auto record : logs) { log_buffer_.push_back(record); } // 2. 日志缓冲区刷新到磁盘 fsync(log_file_fd_); // 3. 数据页写入磁盘 for (auto page : dirty_pages_) { WritePage(page); } }并发控制部分课程会讲两阶段锁2PL和基于时间戳的并发控制。两阶段锁的核心约束很简单事务分两个阶段第一个阶段只能加锁第二个阶段只能解锁锁一旦开始释放就不能再申请新锁。严格遵守 2PL 可以保证冲突串行化但也会引入死锁问题需要死锁检测或超时机制。如果课程内容扩展到多版本并发控制MVCC你会看到更现代的实现读操作不阻塞写操作写操作不阻塞读操作每个事务看到的是数据在某个时间点的快照。MySQL InnoDB 的实现、PostgreSQL 的实现本质上都是在这个方向上做优化。学完事务这一层很多应用层的问题就通了为什么隔离级别从读未提交到可串行化并发性能依次下降为什么基于行锁的数据库在高并发更新同一行时可能出现大量锁等待为什么分布式事务里要实现两阶段提交而不是简单的“先更新 A 库再更新 B 库”因为本地事务日志根本无法保证跨库原子性。4. 关系模型为什么课程从它开始课程把关系模型放在最前面不是走过场而是因为它决定了数据库的整体架构。关系模型由 E.F. Codd 在 1970 年提出核心思想是把数据描述成集合论意义上的关系而不是像早期网状或层次数据库那样面向物理路径。这个抽象看似浅显实际上非常深刻用户面对的是表而不是文件路径数据物理存储方式被完全隐藏。查询语言基于关系代数和关系演算具有数学基础优化器可以做等价变换。数据独立于应用同一组数据可以支撑完全不同的查询需求。学关系模型时一个很值得做的练习是不要只写 SQL而是先画关系代数表达式再转成 SQL最后去数据库里验证结果。比如先列出“所有选课成绩大于 90 分的学生姓名按姓名排序”这个需求先想清楚你需要哪些关系、哪些选择条件、哪些投影列再写 SQL。这个练习看着简单但它训练的是你把自然语言转换成形式化查询的能力而这个能力在后续学查询优化时特别有用。5. 存储引擎实现一个常见的课程实验方向按照“手把手打造数据库”的定位存储引擎部分大概率会配套一个动手实现任务。这里给出一条通用的实现路径同时不绑定任何具体课程要求先实现一个定长记录存储。定义一个表结构每条记录固定长度支持 insert、update、delete、scan 四个基础操作数据写入一个二进制文件。引入页和缓冲池。把文件按页切分缓冲池用哈希表维护页 ID 到内存页的映射实现 LRU 替换和脏页刷盘。加入系统目录。用一张内部表存储表名、列名、列类型、列偏移量让数据库能正确解析“student 表有哪些列每列在哪几个字节”。实现 B 树索引。先用内存版 B 树跑通逻辑再改成磁盘版支持索引页的读入、写入、分裂、合并。把表和索引串起来。插入记录时同步更新索引删除记录时同步删除索引项。这套路径也是很多数据库内核课程的标准作业演进路线。第一个版本可以很粗糙能跑通单线程顺序写入和全表扫描就算成功。后面的优化再逐步考虑并发、事务和崩溃恢复。6. 查询执行建议重点看的几个算子与优化规则查询执行模块信息密度很高看视频时建议带着问题去看。6.1 几个核心算子Seq Scan全表扫描从存储引擎逐页读出记录并应用过滤条件。Index Scan借助索引定位到少量记录再回表取完整数据。Filter在迭代器模型里表现为每次next()时判断是否满足谓词条件。Join内连接、左连接、右连接分别对应不同的输出语义。Aggregation分组聚合需要处理哈希分组或排序分组。6.2 几条核心优化规则谓词下推把WHERE条件尽量往算子树下层移动先过滤再连接减少中间结果集。投影裁剪把不需要的列尽早丢弃减小每行数据的宽度降低 IO 和内存开销。连接顺序优化多表连接时选择基数最小的表先做连接可能带来数量级性能差异。消除冗余算子比如去掉可以合并的 Filter、避免无意义的 Sort。一个值得亲手验证的实验是建两张十万行的表分别测试“先 WHERE 再 JOIN”和“先 JOIN 再 WHERE”两种写法的执行计划差异。多数优化器会自动做谓词下推但你会发现执行计划里的算子顺序不同扫描的行数和最终耗时也不同。7. 事务与并发控制从单体到分布式事务部分的内容建议对照真实数据库来验证。你可以用一个安装了 MySQL 或 PostgreSQL 的本地环境做如下实验开启两个客户端连接设置隔离级别为READ COMMITTED模拟脏读和不可重复读场景。把隔离级别改成REPEATABLE READ观察快照读的行为。在两个事务里交叉更新同一行观察死锁报错信息。查看information_schema.innodb_trx表观察当前未提交事务持有的锁。这些实验能让你对课程里的概念产生直接对应锁是加在索引记录上的、不同的隔离级别影响快照的生成时机、死锁检测需要系统维护等待图。分布式事务是这门课的自然延伸。热词里频繁出现“分布式事务一致性”“seata 分布式事务原理”“订单与库存分布式事务”说明这是数据库应用侧的普遍痛点。课程重点在单机数据库内核层面但学完两阶段锁和 WAL 之后再去看两阶段提交2PC、三阶段提交3PC、基于消息的最终一致性会轻松很多。因为它们的本质问题是一样的如何在没有单点权威的情况下让多个参与者达成一致的提交决策。8. 中英双语学习术语对照与字幕使用建议这个课程带中英双语字幕对中文学习者来说是一个明显加分项。数据库内核是英文术语主导的领域Buffer Pool、WAL、B Tree、MVCC、Isolation Level、Serializable、Deadlock、Checkpoint。如果你只看中文资料能听懂概念但搜英文文档和源码时会对不上号如果只看英文原声术语没问题但复杂句式容易劝退。建议这样利用双语资源第一遍看中文字幕快速建立整体认知。第二遍对照英文字幕把每个模块的关键术语用双语记录下来。第三遍只看课程里出现的代码和图示尝试自己复述一遍实现逻辑。这里给一份高频术语对照表建议直接收藏英文术语中文翻译所属模块Relation / Tuple / Attribute关系 / 元组 / 属性关系模型Relational Algebra关系代数关系模型Buffer Pool缓冲池存储引擎Page页存储引擎B TreeB 树存储引擎Table Scan全表扫描查询执行Index Scan索引扫描查询执行Query Optimizer查询优化器查询执行Volcano Model火山模型迭代器模型查询执行Vectorized Execution向量化执行查询执行Transaction事务事务ACID原子性、一致性、隔离性、持久性事务Two-Phase Locking两阶段锁并发控制Deadlock死锁并发控制MVCC多版本并发控制并发控制Write-Ahead Logging预写日志故障恢复Checkpoint检查点故障恢复Redo / Undo重做 / 回滚故障恢复Serializable可串行化隔离级别Isolation Level隔离级别隔离级别建议你在学习过程中维护一份自己的术语表每学到一个新概念就记录它的英文术语、中文翻译、一句话定义、一个例子。这份术语表后期回看非常值钱。9. 环境准备与动手实践建议跟看视频学数据库内核只看不练很难真正掌握。哪怕课程没有强制要求作业也建议自己搭一个最小实验环境。9.1 推荐的开发环境学习数据库内核实现时C 是最常见的选择也是这套课程大概率采用的工程语言。一个适合初学者的环境组合是组件推荐选择用途操作系统LinuxUbuntu 22.04 或更新版本或 macOS开发与测试编译器g 9 或 clang 11编译 C 代码构建工具CMake 3.16管理工程构建调试工具GDB 或 LLDB排查段错误和逻辑问题IDE 或编辑器VS Code C 插件 / CLion开发效率测试数据库SQLite 或 MySQL / PostgreSQL对照验证行为如果你还没有编译环境下面的命令可以快速在 Ubuntu 上完成最小安装具体版本以官方仓库为准sudo apt update sudo apt install -y build-essential cmake gdb然后创建一个最小工程mkdir db-tutorial cd db-tutorial mkdir src build touch CMakeLists.txt src/main.cppCMakeLists.txt可以先用最小版本cmake_minimum_required(VERSION 3.16) project(DatabaseTutorial) set(CMAKE_CXX_STANDARD 17) add_executable(db_tutorial src/main.cpp)编译并运行cd build cmake .. make ./db_tutorial9.2 最小实验实现一个支持增删改查的内存表建议先从内存表开始不碰磁盘 IO先把逻辑跑通。一个简化实现思路如下#include cstdint #include iostream #include unordered_map #include vector #include string struct Row { int id; std::string name; int score; }; class Table { public: void Insert(const Row row) { rows_[row.id] row; } bool Delete(int id) { return rows_.erase(id) 0; } bool Update(int id, int new_score) { auto it rows_.find(id); if (it rows_.end()) { return false; } it-second.score new_score; return true; } Row* Find(int id) { auto it rows_.find(id); if (it rows_.end()) { return nullptr; } return it-second; } private: std::unordered_mapint, Row rows_; }; int main() { Table t; t.Insert({1, Alice, 85}); t.Insert({2, Bob, 62}); t.Update(2, 90); if (auto* row t.Find(2)) { std::cout row-name row-score std::endl; } t.Delete(1); return 0; }这样做的好处是把“记录如何增删改查”这个最基础的问题先解决掉。当你对内存表已经很熟练时再叠加页、磁盘文件、缓冲池、索引、事务每层都能定位到上一个版本的差异。9.3 性能观察方法学习过程中你需要建立“观察数据库行为”的习惯。最常见的观察手段有EXPLAIN查看执行计划确认查询走了索引还是全表扫描。EXPLAIN ANALYZE查看实际执行时间和行数。用top、htop观察 CPU 和内存占用。用iotop或dstat观察磁盘 IO。自己实现的数据库则用clock_gettime或std::chrono精确统计每个算子的执行耗时。10. 常见问题与学习排错数据库内核课程难度偏高学习过程中出现挫败感是正常的。把常见问题提前列出来能省不少时间问题现象可能原因排查方式解决方案视频里讲的术语听不懂前置知识缺失回顾关系代数、数据结构B 树、哈希表先补《数据库系统概念》前六章或对应中文教材C 代码编译报错编译器版本过旧或标准库差异查看编译器版本和 CMake 配置升级 g/clang确认 C17 标准开启程序运行时崩溃段错误空指针、内存越界用 GDB 跑bt查看堆栈检查指针是否为空、数组是否越界缓冲池替换逻辑失效LRU 链表维护错误打印访问序列和命中率写单元测试覆盖“冷热数据交替访问”场景B 树插入后查询不到数据节点分裂逻辑有误插入少量数据后逐层打印节点内容实现DebugPrint()辅助函数事务并发后数据错乱锁粒度过大或漏加锁写并发测试脚本检查最终一致性先全局锁跑通再改行级锁不了解隔离级别的实际效果概念抽象开两个数据库连接做对照实验参考 MySQL 官方文档中的隔离级别示例视频节奏快、跟不上缺少系统性前置先按中文字幕过一遍再按英文补细节每节视频写 3 条笔记概念、实现、坑分布式事务概念过多单机事务基础不牢先把本地事务、2PL、WAL 弄透再学 2PC、3PC、最终一致性11. 学习路线与最佳实践11.1 推荐的 4 遍学习法第一遍快速过。看中文字幕不暂停不写代码目标是在脑子里建立“数据库到底由哪些模块组成”的整体地图。这一遍不用记笔记看不懂的地方跳过。第二遍跟着实现。对照英文字幕把每一节的代码动手敲一遍。建议不要直接复制课程代码而是自己先写写不出来再看源码。这个阶段是提升最大的阶段。第三遍模块串联。自己画一张数据库架构图从客户端请求到 SQL 解析、执行计划、存储引擎、事务提交把每个模块的输入输出标出来。这张图能暴露很多你以为懂了、实际没懂的地方。第四遍独立扩展。给课程里实现的数据库加一个自己感兴趣的特性。比如加一个新的聚合算子、实现一种新的索引结构、给事务系统加死锁检测。真正常用的能力不是读懂别人的数据库而是改出一个自己能跑的数据库版本。11.2 学习顺序上的优先级建议如果时间有限优先学存储引擎和事务这是数据库内核区别于普通 CRUD 应用的核心价值。如果目标是“看懂执行计划”优先学查询执行和优化规则。如果目标是“在面试中讲清楚 MySQL 事务和 MVCC”优先学事务、并发控制、WAL。如果目标是“理解现代分布式数据库”先把单机事务学透再看分布式事务。11.3 动手实践的工程化建议保留一套“最小可运行”版本。每完成一个功能立刻提交一次 Git 记录这样改坏了随时能回退。建立三个目录src源码、test测试、data数据文件。测试不用引入复杂框架一个简单的断言宏起步就够。每次改动只加一个特性。先能跑再正确最后才优化性能。记录每次实验的耗时和显存或内存占用。自己实现的数据库也一样性能数据是验证优化是否有效的唯一标准。遇到 crash 先复现再用调试器定位不要靠“加打印碰运气”。GDB 的bt命令能直接告诉你崩在哪个函数。11.4 合规与安全边界学习数据库内核是纯技术行为但在学习过程中需要注意以下几点课程和开源项目材料按各自开源协议使用不要将收费课程、带版权的材料二次分发。动手实验时使用自己构建或明确授权的测试数据不要用真实业务数据、个人隐私数据做压测或实验。不要把所学的内核知识用于绕过数据库权限、破坏系统、窃取数据等场景。发布学习笔记或实现代码时明确标注参考来源尊重课程和项目的版权。12. 总结与下一步这门宾州州立团队的数据库内核精讲最值得推荐的点不只是“讲得全”而是它提供了一个从关系模型到事务的完整实现路径。相比于零散地看 B 树、看 WAL、看锁机制的博客跟着一条主线把数据库“手写”一遍知识沉淀会更牢固。如果只给你三个任务我建议按这个顺序执行第一周把关系模型和存储引擎部分过一遍用自己写的一个内存表跑通 insert、delete、update、scan 四个操作。第二周把查询执行部分过一遍用课程或教材里的例子画出一个 SQL 的执行计划树。第三周把事务部分过一遍在 MySQL 或 PostgreSQL 里手工模拟脏读、不可重复读、幻读场景并对照课程里的锁机制解释原因。最容易踩的坑是做“收藏式学习”视频存了很多进度永远停在 5%。数据库内核没有一个知识点是“看会”的全部都是“写会”和“调试会”的。条件允许的话把这个课程和一本经典教材配合使用《数据库系统概念》和《数据库系统实现》都是很好的补充遇到源码层面的问题直接去翻 SQLite 或 PostgreSQL 的源码也是一个很高效的学习路径。下一步的扩展方向也很多想贴近业务可以去学分布式事务、Seata 这类转账场景的解决方案想贴近大数据可以去对比分析型数据库的向量化执行引擎想把关系模型理解得更透可以研究一下国产数据库、Doris、向量数据库在这些理念上的变体。但无论往哪个方向走这门课里的单机数据库内核基础都会是你读源码、看文档时最坚实的那块垫脚石。