南京大学 操作系统 (JYY) 学习笔记:从文件系统到数据库,持久化的终极抽象 (SQL/NoSQL)
写在前面:这是本系列的第二十五篇。
操作系统为我们的应用程序提供了多种持久化控制的机制,包括
fsync等保证崩溃一致性的 API。但如果所有的业务逻辑都要靠手工去调这些底层的 API,那开发者的心智负担就太大了。在此基础上,我们能否在应用程序之上实现一套更为可靠、更为高可用的数据存储抽象?
本讲内容:作为《操作系统》课程的彩蛋延伸,我们将跳出内核的泥潭,探讨数据库系统(SQL 与 NoSQL)的设计原理与实现技术。

文件系统 API 到此结束
作为操作系统留给上层应用的基础原语,文件系统 API 的核心可以概括为四个方面:
- 文件 (数据 & 元数据) 管理:
open,read,write,close,stat,chmod,chown,fsetxattr/fgetxattr,ftruncate… - 目录树管理:
mount,mkdir,readdir,link,symlink,unlink,rename,chdir…- 科普:
rename_ 系统调用是极其强大的,它在很多文件系统底层被保证为绝对的原子操作。_
- 科普:
- 一致性管理:
sync>syncfs>fsync>fdatasync。 - 其他相关 API:
flock,mmap…
关系代数、SQL 和数据库 (Spicy 🌶️)
什么是“应用程序”?
软件是物理世界过程在信息世界中的投影。
- 软件天生有着 Persist Data (持久化数据) 的需求:个人信息(学籍)、订单、交易流水、维护日志……
- 试想一下:如果我们不借助现成的数据库,要如何“从零开始”手搓一个南京大学的教务系统?
- 尽管我们平时总是吐槽学校的教务系统多么难用,但在潜意识里,我们依然敢把成绩和学分数据放心地交给它,这就是持久化带来的信任。
纯手工保存应用数据的痛点
方案 1:在文件(虚拟磁盘)上直接构建数据结构
- 就像定义 ELF、BMP 文件格式一样,自己定义数据的字节排布:
struct superblock {
struct student *s;
struct course *c;
}
struct student { char stuid[16]; ... };
struct course { char cid[16]; ... }
- 任何需求(如
expel(s)退学,enroll(s, c)选课)都需要应用系统自己去翻译成繁琐的read,write,lseek或mmap。 - (注:某些极端的应用确实是这么干的,比如单机游戏存档,或者直接记录 UI 事件的 Replay 快照)。
方案 2:利用目录树
- 用文件夹代表实体,用文件代表属性。
ehall.nju.edu.cn/teacherJxrwApp/22020230/4
├── enrollment
│ ├── 231220001.md
│ ├── 231220002.md
│ └── ...
- 这样 UNIX 世界里的
find,grep等工具都能直接用。 - 但问题接踵而至:如何关联学生和成绩?用
Symlink软链接吗?如何在万人同时抢课时保证并发访问的安全和Crash Consistency?
我们需要的到底是什么?
- Concurrency (并发): 全校一起抢课,系统不能崩。且不能无脑用一把全局大锁,否则几万人的请求排队会直接让系统超时。
- Persistence (持久化): 系统故障,甚至是机房断电,落盘的数据绝对不能丢。
- Atomicity (原子性):
with AllOrNothing(): // 要么全成功,要么全失败回滚
for id in enroll_list:
Path(f'enrollment/{id}.md').write_text('y')
降维打击:Relational Database (关系型数据库)
有没有一个比目录/文件更好用的 API 呢?有,这就是 1981 年图灵奖得主 Edgar F. Codd 提出的关系模型。
“Future users of large data banks must be protected from having to know how the data is organized in the machine.”
(必须保护未来大型数据库的用户,使他们不必去了解数据在机器中是如何组织和存储的。)
- 数据抽象的极致:Everything is a table (万物皆表)。
- 每行代表一个对象;对象可以用
ID(指针) 关联其他对象。这就消解了 C 语言里繁琐的指针链表。
SQL 查询:指针的优雅“配对”
我们不需要写循环去遍历链表了,只需写一条声明式的语句:
SELECT Courses.Title
FROM Student
JOIN Takes_Course ON Student.ID = Takes_Course.ID
JOIN Courses ON Takes_Course.ClassID = Courses.ClassID
WHERE Student.Name LIKE '张%'
数据库系统开启了软件的新时代
ACID 数据库的承诺:
- A (Atomicity, 原子性)
- C (Consistency, 一致性)
- I (Isolation, 隔离性): Strong serializability (强可串行化),无论怎么并发,最终结果都像是一个接一个按顺序完成的。
- D (Durability, 持久性): Strong crash consistency,系统断电 Crash 也绝不会损坏或丢失数据。
学过了《操作系统》底层机制的你,应该很好理解这背后的技术含金量。数据库不仅帮你做了“一把大锁保平安”的效果,还在底层疯狂优化实现了大规模并行的恐怖性能,并且附赠了基于 Redo Log 的完全自动崩溃恢复。
把应用数据交给数据库,对于程序员来说就是一劳永逸。只要不是大到“国民级”的逆天并发应用,关系数据库(分库、分表、B+树索引、读写分离)全都能帮你搞定。
关系数据库:底层的深渊实现 (Spicy 🌶️)
Database v.s. Compilers (数据库本质是编译器)
实现 SQL 查询,本质上就是在做编译优化(语义等价的 Rewriting)。数据库引擎要把你的 SQL 语句翻译成底层的物理执行计划。
数据库是一个超级复杂的并发程序
- SQL 查询底层对应的无非就是对内存和磁盘的
Write(x)和Read(y)。 - 它必须自己维护一套“磁盘上的数据结构”。
- 它大量使用类似文件系统的 Write-ahead Logging (WAL) 技术来保证 Crash Consistency。
但它的并发控制比操作系统更困难。因为长事务(Transactions)是不可能用简单的 lock/unlock 来实现的(这会瞬间锁死整个库)。
伟大的例子:SQLite
“SQLite is the most used database engine in the world. SQLite is built into all mobile phones and most computers…”
- Android, iOS, macOS, Chrome 等几乎所有平台中都深深内嵌了 SQLite。
- Zero Configuration(无需配置守护进程),发行版自带
libsqlite3.so。它是年轻程序员开发第一个应用的首选。
不用关系的数据库:NoSQL (Spicy 🌶️)
如果要实现“国民级”的应用?
当你的数据达到了亿级、甚至十亿级别,传统的 SQL 就显得吃力了。
SELECT * FROM chat_records
WHERE (sender_id = @user_id1 AND receiver_id = @user_id2)
OR (sender_id = @user_id2 AND receiver_id = @user_id1)
ORDER BY timestamp DESC LIMIT 10
对于国民级社交软件,这样一条“合法且正确”的 SQL,如果碰上没有建立完美索引的亿级表,会瞬间让数据库引擎 CPU 拉满,甚至瘫痪整个系统。
因为系统提供商永远无法预知应用程序员会写出怎样逆天的 Query 语句。 只要提供了功能,就必然会被滥用 $ \rightarrow $ 导致 Performance Bug。这是任何底层 Systems 都无法避免的技术债。
解决方法:做减法!(NoSQL 诞生)
牺牲掉极其复杂的“多表联查 (JOIN)”和“通用性”,换来极简的能力、极其残暴的读写性能和极其容易的横向扩展性。
- Key/Value (键值对): 最简单、最高效。适合缓存、计数器。
- Document (文档型 JSON): NoSQL 实现“国民级”应用的主力。
- Column (列族): 适合海量数据的分析。
- Graph (图): 适合社交网络关系。
- AI 时代的 Vector (向量): 大语言模型检索的基石。
为什么 Key-Value 这么好扩展 (Scale)?
- 只要给 Key 做个 Hash,就可以把数据完美地散列扔到上千台不同的机器节点上。
- 写操作只需 Append-only 追加写入日志。
- 分布式的高可靠瞬间完成。
例如国民级 APP 的点赞功能:
- 应用程序全靠 Key 找数据:
user:{uid} - 历史记录:
user:{uid}:like_history(一个简单的 List) - List 直接支持极速的
append/pop/range,速度极快。
内存数据库的王者:Redis
Eveything is In-memory.
- 基于简单的 GET / SET。
- 极度丰富的数据结构抽象:String, JSON, List, Set, Hash, Sorted Set, Stream。
- 甚至支持复杂的搜索:
FT.SEARCH products "@price:[200 300]"。 - 支持简单的事务:
MULTI/WATCH/EXEC,也支持异步持久化落盘。
持久化 NoSQL 的妥协
很多文档型和列族数据库,为了降低学习门槛,会提供一个 SQL 的“子集”。
通过极简的语句接口,强行限制程序员的滥用,从而在底层保证分布式横向扩展的能力。
- MongoDB (Document): Key $ \rightarrow $ JSON。比如直接创建一个 JSON 并 append 到
user:{uid}.messages。 - Cassandra (Column): 语言叫
CQL,看起来很像 SQL,但功能极其受限。
INSERT INTO messages (user_id, message_id, content, timestamp)
VALUES ('1234567', now(), message, toTimestamp(now()));
总结
Take-away messages:
在操作系统底层为我们提供的文件、目录、网络等“粗糙”的 API 之上,开发者们发挥了惊人的创造力。
我们看到了为了降低心智负担而兴起的关系数据库,看到了为了应对亿级高并发而做减法繁荣起来的 NoSQL,以及今天浪潮正盛的 AI 向量数据库。
在这几波狂暴的技术浪潮之间,虽然操作系统的内部实现发生过天翻地覆的重构,但操作系统的 API 却相当惊人地保持了稳定(POSIX)。正是这种数十年的“稳定性”,支撑起了上层应用生态无所顾忌的繁荣。这也是操作系统作为一切软件“基石平台”最伟大的使命。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐


所有评论(0)