AIGC标识 SQLite 的页结构与 WAL 机制:为什么 busy_timeout 设了 30 秒还报 database is locked

一个 SQLite 数据库文件打开,本质上就是一串 4096 字节长的页。第一页是 100 字节的文件头,后面紧跟着 B-tree 根页、叶子页、溢出页,所有表和索引都长在这串页里。删一条记录,磁盘上的字节并不会立刻变少;那一页被摘下来,挂进一条叫 freelist 的链表,等下一次插入时再从链头上摘下来复用。

很多人用了很多年 SQLite,对它的认知停留在“一个文件就是一个库”。真到了并发写报错、文件只增不减、checkpoint 看不懂的时候,才发现页结构这层从来没拆开看过。

图片11

B-tree 页的填充与分裂:删出来的洞去哪了

SQLite 的表用 B-tree 组织,默认页大小 4096 字节,建库时一旦定下来就不会自动改。一页里装着若干个单元格,每个单元格是一条记录或者一个索引项,按键排序紧凑堆放。往一页里插入新记录,单元格数量超过页的填充水位,这一页就分裂成两页,一半记录搬过去,父页里多一个指向新页的指针。

删除操作走的是另一条路。被删掉的记录不会触发页合并,那一页只是变成半满,挂到 freelist 上等待复用。 这就解释了一个常见现象:批量删除几十万行之后,数据库文件大小纹丝不动,甚至继续变大。freelist 上的页不会主动还给操作系统,除非手动执行 vacuum,或者用 incremental_vacuum 一段一段归还。

freelist 是一条跨页链表,每个被释放页的前 8 个字节指向下一个空闲页。下次插入时,SQLite 先在 freelist 链头上摘一页出来当新叶子页,而不是把文件尾部撑大。所以文件大小稳定不下来,往往不是泄漏,是 freelist 里的页还没被新数据吃掉。

WAL 帧与 checkpoint:写入先写到哪里

开了 PRAGMA journal_mode=WAL 之后,写操作不再去改原数据库文件,而是追加到一个跟在旁边的 -wal 文件里。每一条记录在 WAL 里就是一帧:24 字节帧头记录校验和与页号,后面紧跟着那一整页的完整内容。一个事务提交,就把若干帧一次性追加进去,再在帧尾写一个提交记录。

读操作走的是另一套逻辑:读某一页时,先看 WAL 里有没有比数据库文件更新的版本,有就用 WAL 里的,没有才读主库文件。这就是 WAL 读写不互斥的全部秘密——读者永远读自己开始那一刻看到的那一份页快照,写者往后追加自己的帧。

WAL 文件不会无限长。默认配置下,WAL 累积到 1000 页左右会触发一次自动 checkpoint,把里面的帧回写到主库文件,再把 -wal 文件截断。checkpoint 也可以手动用 PRAGMA wal_checkpoint 触发,3.51.2 之后还加了 NOOP 参数,用来在不真正做回写的情况下探测 WAL 状态。今年 10 月 16 日发的 3.54.1 是个补丁版本,主要修的是 32 位构建上一个堆缓冲区溢出问题,64 位环境不受影响——升级这件事,看清楚补丁范围再决定动不动生产环境。

图片12

busy_timeout 为什么会“被忽略”

WAL 解决了读写互斥,但写者和写者之间还是串行的。整个数据库同一时刻只允许一个写事务持有 WAL 写锁。 两个进程同时写,后来那个立刻拿到 SQLITE_BUSY。

直觉的修法是 PRAGMA busy_timeout=30000,让连接等 30 秒。但社区里反复出现一种现象:30 秒超时设了,错误还是秒级抛出来。原因在于事务起点。默认的 BEGIN 是 deferred 模式,事务开始时并不拿任何锁;等到事务里第一条写语句真正要拿写锁时,如果另一个写者已经持有,SQLite 直接返回 busy,busy handler 根本没机会介入等待。

正确写法是 BEGIN IMMEDIATE。一开始就尝试拿写锁,拿不到就在 busy_timeout 范围内乖乖等,而不是走到一半才发现锁被占了。 慢活放到 BEGIN IMMEDIATE 之前做完,事务里只留真正写库的那几行,锁持有时间压到最短。

单文件数据库在什么场景反而更合适

把这些机制摊开,SQLite 适合什么场景就清楚了。嵌入式设备、桌面应用、单机小工具、本地缓存、几百兆以内的业务库,单文件备份就是拷贝一份,恢复就是拷回来,没有进程依赖,没有端口占用,没有主从切换。官方给出的经验阈值是每天 10 万次点击以内的站点,SQLite 完全扛得住。

它不适合的场景也很明确:写并发上到四五个进程以上、单库突破几十 GB、需要跨机分布式一致性、长事务频繁跨分钟。这些场景换 PostgreSQL 或者 MySQL,不是 SQLite 不够努力,是它的页结构和 WAL 设计从一开始就没朝那个方向长。

本文为行业科普分享,所涉观点仅代表作者个人理解,不构成任何投资、消费、医疗建议。文中提及的产品、地点、数据均来自公开渠道或作者亲身体验,或随时间变化。读者据此操作风险自担。

posted @ 2026-11-05 14:55  深入浅出技术栈  阅读(8)  评论(0)    收藏  举报