数据库文件通常不是一行行可读文本,而是数据库自己设计的一套二进制磁盘结构。核心单位是页;表数据、索引、空闲空间、事务日志和元数据一起配合,才补出"可靠账本"这个抽象。
我追问的链
- 普通文件也能写数据,为什么还需要数据库?
- 表、行、列在文件里到底怎么落下去?
- SQL 说"我要这个用户",数据库怎么找到对应字节?
- 索引为什么能加速?
- 事务为什么能做到"要么都成功,要么都失败"?
- 数据库文件里总不能是文本吧,那它到底怎么存?
1. 普通文件为什么不够用
普通文件当然能存数据,比如:
1 | 1,Alice,alice@example.com |
但真实业务一上来,就会撞到几个硬问题:
- 并发写入会乱:两个人同时改同一个文件,谁先谁后?会不会写一半被另一个覆盖?
- 查找很慢:几千万行里找一个邮箱,普通文本文件通常只能一行行扫。
- 部分失败危险:转账时 A 扣钱成功,B 加钱前程序崩了,普通文件不会自动回滚。
- 结构没约束:邮箱能不能重复?余额能不能小于 0?普通文件本身不管这些规则。
- 恢复困难:写到一半断电,文件可能处在半新半旧的状态。
所以数据库不是因为"文件不能存",而是因为普通文件缺少一整套并发、查询、约束、恢复、事务机制。
2. 表、行、列抽象了什么
关系型数据库把现实世界拆成表:
1 | users |
- 表:一种事物,比如用户、订单、文章。
- 行:一个具体对象,比如 Alice 这个用户。
- 列:对象的属性,比如名字、邮箱、创建时间。
- 主键:这一行的唯一身份,比如
id = 1。 - 外键:表和表之间的关系,比如订单属于哪个用户。
订单表可能这样:
1 | orders |
orders.user_id = 1 指向 users.id = 1。这就是数据库的第一层抽象:把现实里的对象和关系,压成一张张有规则的表。
3. SQL 怎么变成磁盘读取
你写:
1 | SELECT * FROM users WHERE email = 'alice@example.com'; |
数据库内部大概会走:
1 | SQL 文本 |
SQL 是声明式的。你只说:
我要 email 等于 alice@example.com 的用户。
你没有说从哪个文件读、第几个字节开始读、要不要用索引、先扫哪张表。这些由数据库根据表大小、索引和统计信息来决定。
没有索引时,可能是:
1 | 全表扫描:从第一行扫到最后一行 |
有 email 索引时,可能是:
1 | 先查索引,快速定位到对应行,再读那行数据 |
4. 文件里不是文本,而是页
数据库文件通常是二进制格式,核心单位是 页,也叫 page 或 block。
1 | 数据库文件 |
数据库不会每次只读一行。磁盘太慢,它通常一次读一整页,比如 4KB、8KB、16KB。MySQL InnoDB 默认页大小常见是 16KB,PostgreSQL 常见是 8KB。
一页内部大概像这样:
1 | Page Header |
Alice 这种字符串内容本身可能还是 UTF-8 字节,但整份文件不是可读文本,而是一堆有结构的二进制块。
一行在文件里可能不是:
1 | 1,Alice,alice@example.com |
而更接近:
1 | [行头][字段数量][null 标记][id 的二进制值][name 长度][name 字节][email 长度][email 字节] |
这样存,是为了:
- 快速定位:知道第几页、第几个偏移就能找到数据。
- 节省空间:数字直接用二进制存,比文本更紧凑。
- 支持变长字段:字符串、JSON、TEXT 可能长度不同。
- 支持更新和删除:页里要管理空洞、空闲空间和行版本。
- 支持索引结构:B+Tree 节点也要存在页里。
- 支持崩溃恢复:数据页还要和日志文件配合。
5. 索引也是页:B+Tree 存在文件里
索引文件也不是文本。B+Tree 的每个节点通常也是一页:
1 | 索引页 |
查 email = 'bob@example.com' 时,数据库不是打开文本搜索字符串,而是:
1 | 从索引根页开始 |
所以索引像一本书的目录:不用从第一页翻到最后一页,而是先走目录,直接跳到目标附近。
索引的代价也很真实:
- 查得快。
- 写入变慢,因为插入 / 更新数据时,索引也要更新。
- 占空间,因为索引本身也是一套数据结构。
- 索引太多会拖慢写入和维护。
一句话:索引用额外空间和写入成本,换查询速度。
6. 运行时先查内存:Buffer Pool
数据库不是每次都直接去磁盘文件里翻页。真正运行时,中间还有一层关键结构:内存缓存,常叫 buffer pool 或 page cache。
一次查询大概是:
1 | SQL |
所以磁盘上是一页一页存的,数据库平时主要是在内存里的页副本上工作。
1 | 磁盘文件: |
更新数据时,也通常不是立刻把整页写回磁盘:
1 | 1. 把 page 42 从磁盘读进内存 |
这就是为什么数据库可以又快又可靠:读写热点尽量在内存里完成,但可靠性由日志兜底。
7. 先写日志,再改账本
如果数据页还没刷回磁盘,机器突然断电怎么办?
数据库靠日志兜底。核心思想叫 Write-Ahead Logging,也就是:
先记账,再改账本。
大致流程:
1 | 先写日志: |
如果崩了,重启后数据库读日志:
- 这个事务已经提交了,但数据页没来得及写:重放日志。
- 这个事务没提交:撤销它。
所以磁盘上不止有"表数据文件",通常还有:
1 | 数据文件:真正的表和索引页 |
数据库维护的是几套东西的配合:
1 | 表数据:现在是什么 |
逻辑闭环 / 锚点
数据库文件不是文本,而是数据库自己设计的二进制磁盘数据结构。它把普通文件和磁盘这种朴素能力,包装成一个更强的抽象:
- 页:让磁盘读写有稳定单位。
- 行格式:让对象属性能落成可解释的字节。
- 索引:让查询不用全表扫描。
- Buffer Pool:让热点数据在内存里快读快改。
- WAL / redo log:让崩溃后能恢复。
- undo / 事务信息:让回滚和并发读取可控。
一句话:
现实里磁盘慢、机器会崩、请求会并发;数据库在文件之上补出一个像账本一样可靠、可查询、可恢复的系统。
关联
- HTTP / 缓存 / Cookie / Session:Cookie / Session 在无状态 HTTP 上补出身份连续性;数据库在普通文件上补出可靠账本。
- 负载均衡 / CDN:应用服务器可以横向扩展,但长期业务事实通常要落到共享数据库。
- 母题 在更弱的底座上补出更强的抽象。
来源:与 Codex 的对话,2026-07。