首页 / SQLite 入门教程 / SQLite 架构与存储格式简介

SQLite 入门教程

SQLite 架构与存储格式简介

本教程共 50 篇 · 第 49 篇 · 更新于 2026-07-31

sqliteB树存储架构

49. SQLite 架构与存储格式简介

本节目标:学完本章你能用大白话解释”一个 .db 文件里到底装了什么”,并理解页(page)、B 树、游标(cursor)这些底层概念,知道为什么 SQLite 又快又稳。

前面所有章都在”用”SQLite,这一章我们掀开引擎盖,看看它内部是怎么工作的。放心,这是原理章但只讲直觉,不要求你写 C 代码。理解了底层,你对”为什么建索引能加速""为什么单文件也能扛百万行”会有更稳的判断力。

先回到那个最熟悉的定位:SQLite 是 serverless(无独立服务进程)、零配置 的。这意味着你电脑上的 demo.db 不是一个”要连的服务器”,而是整个数据库本身就是那个文件——没有后台 daemon、没有网络端口、没有单独的进程在跑。你的程序调用 sqlite3 库时,库直接打开这个文件、按它自己的格式读写。这和 MySQL/PostgreSQL 那种”客户端 → 服务器进程 → 磁盘文件”的三段式架构截然不同。

一、数据库 = 一个文件 = 一串”页”

SQLite 把整个数据库文件切成固定大小的块,叫页(page)。默认每页 4096 字节(4KB),这是它设计里非常经典的一个数字;页大小也可在建库时设为 512–65536 字节之间 2 的幂(如 1024 / 2048 / 8192 / 16384 / 32768 等)。文件开头是第一页,接着第二页、第三页……整库就是”页的线性排列”。

为什么是页?因为磁盘读写按块进行最高效。SQLite 不会为了存一条小记录去折腾整个文件,而是定位到某一页、只改那一页。页和页之间用”页号”编号,像书的一页一页,所以叫”页”。

每页有自己的”角色”:有的页存真正的数据行,叫叶子页(leaf page);有的页不存数据、只存”目录指针”指向下层页,叫内部页(interior page)。这套”目录套目录”的结构,就是下面的 B 树。

重点提示

页(page)是 SQLite 磁盘管理的最小单位;4096 字节是默认页大小,创建数据库时定下后一般不再变。你平时用的 .dump、VACUUM 等命令,本质上都是在按页搬运、重组这些块。

二、B 树:让查找飞起来

关键点来了:SQLite 不是把行”一排排平铺”在文件里,而是用 B 树(B-tree) 来组织。B 树是一种自平衡的树形结构,专门为多、快、狠地查找/插入/删除而设计。

打个比方:如果数据是一本电话簿,平铺查找就像从第一页翻到最后一页找人,慢;B 树则像书前的”目录 → 章节 → 页”三级索引,每次比较就把范围砍掉一大半,翻几次就到位。哪怕表里有几百万行,查找深度通常也只有个位数层级,所以速度几乎不随数据量线性变慢。

具体来说:每张表(以及每个索引)背后都对应一棵 B 树。官方把”表的 B 树”叫 table b-tree、“索引的 B 树”叫 index b-tree,二者都存放在同一个 .db 文件里。树的节点就是前面的”页”——叶子页放真实的数据行,内部页放”键值到子页”的路由。补一句关键机制:每张普通行表(rowid table)的 B 树,是以一个隐藏的 64 位整数 ROWID 作为键来组织的;你写 INTEGER PRIMARY KEY 时,这一列就是这把 ROWID 的别名(回顾第 12 章),所以按主键查本质上就是拿 ROWID 在 B 树里直接定位,难怪那么快。WITHOUT ROWID 表(较少用)则改用索引式 B 树、不分配 ROWID。当你 WHERE id = 42 时,SQLite 从根页出发,沿 B 树一路下钻,几步就定位到存 (42, ...) 的那一页,根本不用扫描全表。这也就是为什么第 34、42 章强调”索引能加速”——索引本质上就是另一棵按查询列组织的 B 树。

三、行是怎么塞进页的

在一页内部,SQLite 把一条条记录序列化成紧凑的二进制,叫单元(cell)。每个 cell 存一行,包含各列的值。这里又呼应我们反复讲的类型体系:列声明只是”类型亲和性(Type Affinity)“建议,实际落盘的存储类(Storage Class)只有 5 种——NULL / INTEGER / REAL / TEXT / BLOB。日期没有专门类型,按 TEXT(ISO8601)、INTEGER(unix 秒)或 REAL(儒略日)存进去。

一张页塞满后,B 树会分裂出新的页,并把新路由写回上层内部页——这一切由 SQLite 在写入时自动完成,对你完全透明。页按需分配、只在需要时申请,所以空表和满表都是”同一个文件、合理大小”。

四、游标与事务:怎么读到、怎么回滚

为了遍历 B 树,SQLite 内部用一种叫游标(cursor) 的结构,它像个”在页之间移动的书签”,能定位、前进、读当前行。你写的 SELECT 最终都被编译成”移动游标 + 取 cell”的操作。

至于”为什么事务能回滚”,底层靠的是:改动先写进日志——要么是传统的 rollback journal(回滚日志),要么是较新的 WAL(Write-Ahead Log,写前日志)。这两种机制同一时刻只启用一种(一个库要么用回滚日志、要么用 WAL,不会同时用);真正提交时才把页刷进主文件,中途崩溃时,重启的 SQLite 拿日志把没完成的页恢复回去,保证”要么全成、要么全不成”的原子性。这也是 COMMIT / ROLLBACK(第 36 章)能稳如泰山的底气。

五、为什么”单文件”既简单又够用

把全部状态收进一个文件,带来三个好处:第一,备份就是复制文件——cp demo.db demo.db.bak 即可,无需导出导入(第 45 章讲过)。第二,跨平台零摩擦:同一个 .db 在 Windows、Linux、手机上都能直接打开,字节级兼容。第三,嵌入式友好:App 打包时把这个文件打进去就行,运行时无需配置服务器。

实用技巧

想知道文件内部布局,可用 sqlite3 demo.db "PRAGMA page_size;" 看页大小,PRAGMA page_count; 看总页数,PRAGMA freelist_count; 看空闲页。这些 PRAGMA(第 41 章)就是 SQLite 暴露出来的”内部仪表盘”。

六、类比小结

.db 文件想成一本带智能目录的活页本:整本书是一份数据库;每张活页是一页(page,默认 4KB);书按 B 树目录组织,让你秒查任意条目;每页上记着一行行紧凑的笔记(cell,存 5 种存储类之一);书签(cursor)帮你翻页;而”先写草稿纸、确认无误再誊正”的机制,就是事务回滚。正因为整本就是”一个文件”,你随身带着、随处翻开、直接复印,都不需要书店(服务器)在场。

常见坑

第一,别把”单文件”误读成”不可靠”——SQLite 用日志/WAL 保证崩溃恢复,广泛用于飞机、手机、浏览器,可靠性极高。第二,页大小建库即定,想改要重建数据库。第三,虽然数据库是文件,但不要在程序运行时用编辑器或其他进程直接改它——应通过 SQLite API 访问,否则易损坏;并发写要靠 SQLite 自己的锁机制(第 36 章),而不是你自己抢文件。

理解了”文件 → 页 → B 树 → cell”这条主线,你对前面学的索引、事务、VACUUM、EXPLAIN QUERY PLAN 都会更通透。下一章我们把 50 章知识收个尾,给你一张完整的进阶地图。