把 SQLite 跑上生产:四个参数和一个备份底线

小项目上线的剧本通常有两种:一种是被「生产必须上 Postgres」的说法说服,在还没遇到任何问题时,先背上数据库服务的安装、监控、调优成本;另一种是什么都不配直接上,流量一来,日志里开始刷 database is locked。
SQLite 的真实定位其实在两者之间:单机、读多写少的应用,配好几个参数就能稳定运行。它的问题从来不是「不能用」,而是默认配置按 1990 年代的单机嵌入式场景设计,直接搬上 Web 生产环境必然水土不服。这篇文章把上生产前真正要做的几件事理清楚。
参数调得再好,也救不了选错场景。SQLite 的适用边界很清晰:
| 维度 | 适合 | 不适合 |
|---|---|---|
| 部署形态 | 单台服务器,应用和库文件同机 | 多台机器共享同一个库文件 |
| 负载特征 | 读多写少:内容站、博客、内部工具 | 每秒成百上千笔并发写入 |
| 运维需求 | 备份靠复制文件、无需主从复制 | 需要实时复制、多活 |
| 存储 | 本地磁盘 | 网络文件系统 |
最后一行是硬红线:WAL 模式依赖共享内存文件做跨进程协调,NFS 上的锁机制实现不可靠,官方文档明确警告可能导致数据库损坏。VPS 和独立服务器不用操心,但部分共享主机的用户目录 quietly 挂在 NFS 上,上库文件前务必确认存储类型。
至于「多大规模该换 Postgres」,判断依据是并发写入:当多个进程持续排队写、timeout 调到几十秒仍报锁,或者业务需要跨机复制时,才是迁移的真正信号。读多写少的内容站,大概率一辈子用不到。
默认配置的锁问题出在三处,逐一对应:
| 默认值 | 问题 | 改法 |
|---|---|---|
| journal 模式 DELETE | 写入时读全部阻塞,读写互斥 | 开 WAL,读写并行 |
| 事务模式 DEFERRED | 事务先当读者、写到一半升级写锁,升级瞬间被抢直接报错,连 timeout 都不等 | 改 IMMEDIATE,开事务就拿写锁 |
| busy_timeout 为 0 | 锁被占立刻报错,不等待 | 设 timeout,锁冲突时排队重试 |
WAL(预写日志)的原理不复杂:写操作先追加进独立的 -wal 文件再合并回主库,读者读主库加日志,于是读不再阻塞写、写不再阻塞读,代价是同一时刻仍只有一个写者。IMMEDIATE 则是把「先读后升级写锁」的竞态直接消掉:有开发者做过对比压测,只加这一个参数,错误率从每秒几十笔掉到接近零。
Django 5.1 起这三个参数都能在配置文件里直接声明,下面是最小可用版本:
为什么这么写:WAL 是持久设置,写一次就记录进库文件;其余 PRAGMA 是每个连接各自生效,所以放进 init_command,每次新建连接自动执行,谁也不会漏。journal_size_limit 把 WAL 文件封顶在 64MB,防止写入高峰时它无限膨胀。旧版 Django 没有这两个选项,需要自定义数据库引擎或用连接信号执行 PRAGMA——与其修补,不如先升级。
一个容易忽略的连带影响:如果项目开了 ATOMIC_REQUESTS(每个请求包一个事务),配上 IMMEDIATE 之后连纯读页面都要先拿写锁,全站请求开始排队,读多写少的站反而变慢。更好的做法是关掉它,只把真正需要事务的写操作包进 transaction.atomic()。
落盘策略 synchronous 是唯一需要算一笔安全账的参数:
关键纪律是配对:NORMAL 的安全性建立在 WAL 之上。如果不开 WAL 只设 NORMAL,掉电时损坏的是真正的风险。对内容站来说这笔交易几乎无感——丢几秒的最新评论可以接受,库文件损坏才是事故。
参数配好只保证了「不坏」,备份才决定「坏了能不能救」。最常见的错误做法恰恰是最直觉的:写个 cron 对着 .db 文件跑 cp。应用正在写入时复制文件,拷出来的备份可能是事务上不一致的坏文件,平时看不出来,恢复那天才发现是废纸。
SQLite 内置了两个安全备份命令,都不用停服:
VACUUM INTO 在一个事务内拍快照,顺带整理碎片、缩小体积;.backup 命令效果等价。之后把压缩文件按滚动命名(如按日期)传到对象存储,异地备份就有了。
要求恢复点更小的场景(比如用户数据频繁变更),可以上 Litestream:它作为常驻进程盯着 WAL 文件,把每次写入增量流式复制到 S3 兼容存储,恢复点从「昨晚」缩短到「几秒前」,一条 litestream restore 命令即可回滚。
最后一条底线:没验证过恢复的备份不叫备份。定期下载一份备份,跑 PRAGMA integrity_check,再真的把应用指向它启动一次——测试的成本几分钟,赌注是整个数据库。
SQLite 从来不是「不能上生产」,而是「默认配置不能上生产」。划清场景边界、配好 WAL 与事务模式、算清落盘的安全账、守住不用 cp 备份的底线,这四个参数加一条纪律,就够一个小项目安安稳稳跑上很久。省下来的数据库运维时间,拿去做真正有价值的事。
如果这篇文章帮你省了一次多余的 Postgres 部署,欢迎转发给正在纠结的同事。
#Django #SQLite #后端开发 #数据库 #Web开发 #服务器运维