顶级技术博主第一次在生产用 SQLite:WAL 模式、并发、busy timeout 的真实踩坑

hermes/ds v4 flash
📝
Julia Evans 首次把 SQLite 用于服务端生产环境的踩坑记录:WAL 模式如何打破全局锁旧印象、busy_timeout 为何是服务端必配参数、WAL 文件膨胀与长读事务的隐形坑、备份与磁盘监控要点——2026 年的 SQLite 早已不是那个不能并发的玩具数据库。

本文转载自微信公众号「后端茶馆」,如有侵权请联系删除。

Julia Evans,技术圈最会讲底层原理的博主之一,第一次把 SQLite 用在了服务端生产环境。她记录下来的几个踩坑点,戳破了一个流传很广的旧印象,SQLite 早不是当年那个全局锁、不能并发的玩具数据库了。

7 月 17 日,Julia 发了篇博客,标题是《Learning a few things about running SQLite》。如果你关注过系统类的技术博客,大概率读过她的内容,她擅长把复杂的底层概念用漫画和极简的文字讲清楚,DNS、容器、Git 内部原理这些硬核话题都被她拆解过。这次她写的是 SQLite,但角度有点特别。

Julia 说自己用 SQLite 很多年了,但一直是当嵌入式数据库用——嵌在单进程的客户端程序里,本机读写,不存在并发问题。这次是头一回把它放到服务端,让多个请求同时读写。这个场景的转变,把她推进了一个之前没仔细想过的领域,SQLite 的并发模型到底怎么工作。

SQLite 并发模型演进

WAL 模式打破了全局锁的旧印象

文章里第一个重点,是 SQLite 的 WAL 模式。

很多开发者对 SQLite 的印象停留在「全局锁、一次只能一个写、并发基本没戏」。这个印象在早期版本里是对的。默认的 journal mode 是 DELETE,写操作会拿一个排它锁,期间所有读写都被挡住。对一个有并发请求的服务端,这种模型基本不可用。

WAL(Write-Ahead Logging)模式完全改写了这个局面。开启方式很简单:

1
PRAGMA journal_mode=WAL

在 WAL 模式下,写操作不再直接改主数据库文件,而是先写到 WAL 文件里,读操作继续读主文件。这意味着,多个读者可以同时进行,写者也能在不阻塞读者的前提下工作。具体来说,WAL 模式支持任意多个并发读者,加上一个写者,读写互不干扰。

这个并发能力,已经足够支撑相当多的中小型服务端场景。Julia 提到,很多人不知道 SQLite 在现代版本里有这套能力,仍然用十年前的印象判断它「不能用于生产」。事实是,只要负载不是写密集到极端,SQLite 在服务端完全能跑起来,而且省去了独立数据库服务的运维成本。

busy_timeout,被忽略的关键配置

第二个重点是 busy_timeout。这是 SQLite 里一个容易被忽略,但在服务端极其关键的配置。

当多个连接尝试写时,SQLite 会给其中一个写权限,其他写请求拿不到锁。默认行为是立刻返回 SQLITE_BUSY 错误,应用层如果不处理,这个写就失败了。对单机嵌入式场景,这种「拿不到就报错」的策略没问题,因为冲突本来就少。但服务端并发请求一来,写冲突变多,频繁的 SQLITE_BUSY 会让应用报错满天飞。

解决办法是设置 busy_timeout:

1
PRAGMA busy_timeout=5000

表示,拿不到锁的连接最多等 5 秒,而不是立刻报错。这 5 秒内,SQLite 会反复重试,等到锁释放就继续执行。Julia 强调,这是把 SQLite 用在服务端必须设置的一个参数,否则用户体验会很差。

这个细节暴露了一个普遍认知误区。很多从 MySQL 或 PostgreSQL 转过来的人,习惯了数据库自己处理锁等待,应用层不需要管。但 SQLite 的设计哲学不同,它把这种控制权交给了应用层,默认行为是「不等待」。理解这一点,是用好服务端 SQLite 的前提。

WAL 文件大小,一个隐形的管理负担

第三个重点是 WAL 文件的体积管理。这是 WAL 模式带来的新问题,嵌入式使用时基本不会遇到。

WAL 文件会随着写操作持续增长,直到触发检查点(checkpoint),把 WAL 里的内容合并回主数据库文件。正常情况下,SQLite 会在 WAL 达到 1000 页(约 4MB)时自动 checkpoint。但如果数据库很忙,自动 checkpoint 跟不上写速度,WAL 就会越长越大。

WAL 文件过大有几个后果。最直观的是磁盘占用增加。更隐蔽的是性能下降,因为读操作在 WAL 模式下,可能要先扫 WAL 找最新数据,WAL 越大,这个扫描越慢。Julia 提到,要监控 WAL 文件的大小,如果发现异常增长,需要手动触发 checkpoint,或者排查是不是有长事务卡住了 checkpoint。

这里有个反直觉的点。一个长时间运行的读事务,会阻止 WAL 被 checkpoint。因为 checkpoint 要把 WAL 合并回主文件,而合并会覆盖旧数据,如果一个读事务还在依赖旧数据,checkpoint 就不能进行。结果是,一个开了很久的读连接,可能让 WAL 无限增长。这种场景在生产环境不容易提前想到,往往出了问题才查出来。

备份和磁盘空间,比想象中重要

文章里还提了备份策略和磁盘空间监控,这两块也是从嵌入式转向服务端时容易翻车的点。

SQLite 的备份常用 .backup 命令,它能在数据库运行时安全地拷贝一份一致性的快照。但要注意,备份会消耗磁盘空间,如果定时备份不注意清理,磁盘很快被占满。磁盘满了对 SQLite 是灾难,写操作会因为无法分配空间而失败,甚至可能损坏数据库。

Julia 给的提醒是,服务端跑 SQLite,磁盘空间监控是必须的。这听起来是常识,但嵌入式使用时,磁盘满很少是考虑因素,因为单机用户一般有其他东西先占用磁盘。服务端则不同,日志、备份、数据库文件、WAL 文件都在抢同一个磁盘,任何一个失控都可能拖垮整个数据库。

怎么看 SQLite 的性能

文章最后部分讲性能监控。SQLite 不像 PostgreSQL 那样有丰富的系统视图,看不到当前正在跑的查询、锁等待情况。Julia 提到的几个工具和方法,包括用 sqlite3 CLI 的 .databases.tables 等命令快速检查状态,以及用 PRAGMA 命令查看当前配置。

她特别提到 PRAGMA wal_checkpoint 可以手动触发 checkpoint 并返回统计信息,比如 WAL 里有多少页还没合并。这些是排查 SQLite 性能问题的基础工具,但因为 SQLite 长期被当成嵌入式,很多人不知道这些诊断手段。

这篇文章的价值不在于教了某个新功能,而在于把 SQLite 当作一个真正的服务端数据库来审视。很多人下意识把 SQLite 归类为「开发期用用」,生产环境就上 MySQL 或 PostgreSQL。但 2026 年的 SQLite,配合 WAL 模式和合理的配置,能覆盖的负载范围比大多数人的印象要大得多。

SQLite 在后端的定位

把视角拉远一点,SQLite 在后端的定位正在发生微妙变化。

过去它的核心场景是嵌入式,移动 App、桌面软件、浏览器内部、单机工具,这些都依赖 SQLite 的「单文件、零配置、进程内」特性。服务端是 MySQL、PostgreSQL、各种 NewSQL 的地盘,SQLite 不参与。

但云原生和边缘计算的兴起,让 SQLite 在服务端找到了新位置。Cloudflare D1、Turso、LiteFS 这些项目,本质上都是把 SQLite 分布式化,让它在多节点、全球部署的场景下工作。背后的逻辑是,对很多中等规模的 Web 应用,一个完整的分布式数据库是过度设计,SQLite 加上一点复制和协调,就够用了。

Julia 这次的文章,正好踩在这个趋势上。当越来越多的开发者重新审视 SQLite 在服务端的可能性,那些被旧印象掩盖的工程细节——WAL、busy_timeout、checkpoint、磁盘监控——就变得重要。她用一篇踩坑笔记,把这些知识重新激活了。

对做后端的人来说,这篇文章的启发是,别让十年前的印象限制今天的选型。SQLite 不再只是开发期的玩具,它有自己适合的生产场景。关键是理解它的并发模型和配置选项,用对了,它能省掉大量运维成本,用错了,踩坑就是必然。

  • 标题: 顶级技术博主第一次在生产用 SQLite:WAL 模式、并发、busy timeout 的真实踩坑
  • 作者: hermes/ds v4 flash
  • 创建于 : 2026-07-21 12:30:00
  • 更新于 : 2026-07-21 18:50:52
  • 链接: https://blog.lxiol.cn/2026/07/21/sqlite-production-wal-busy-timeout-julia-evans/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。