← 全部笔记

SQLite 用在单机服务里,什么样的场景合适

2026-10-05 编程与语言 约 6 分钟

我维护过一个单机服务,数据量不到十万行,QPS 峰值两位数,一直用 SQLite。 期间被问过很多次「为什么不用 MySQL」,也踩过几个坑。这篇把判断标准写清楚。

先破除一个印象:SQLite 不是不能并发

常见的说法是「SQLite 只允许一个连接」,这不准确。准确的说法是: 同一时刻只允许一个写事务,读可以很多个并发(在 WAL 模式下)。 对于读多写少的场景,这完全够用。

PRAGMA journal_mode = WAL;     -- 读写不再互相阻塞
PRAGMA synchronous = NORMAL;   -- WAL 下的推荐值,兼顾安全与性能
PRAGMA busy_timeout = 5000;    -- 锁等待 5 秒,别用默认的 0
PRAGMA foreign_keys = ON;      -- 默认是关的,容易忘

busy_timeout 那条最关键。默认值是 0,意味着遇到锁立刻抛 database is locked。很多人以为这是 SQLite 撑不住,其实只是没设等待。

WAL 带来的两个副作用

WAL 会把改动先写进同目录的 -wal 文件,于是有三件事要注意:

  • 不能只拷主库文件。单拷 app.db 会丢掉还在 WAL 里的数据。
  • 文件变多:-wal 和 -shm 要一起管理,脚本里 mv 整个目录更稳。
  • 不支持网络文件系统。放在 NFS 上会出错,只能用本地盘。

如果为了「备份时可以直接 cp」而退回 journal_mode=delete, 读写会互相阻塞,但迁移和备份确实简单很多。这是个真实的取舍,不是谁更先进的问题。

备份只有一种正确做法

无论什么模式,都别直接拷正在被写入的库文件:

sqlite3 app.db ".backup /tmp/app-backup.db"

这条命令内部会拿一致性快照,即使服务在跑也是安全的。 更好的做法是先停写入再备份——如果业务允许一个几秒的窗口,这是最省心的。

反例:先备份、后停服务,中间那段写入会丢,而且两边的数据会分叉。 这个顺序在迁移服务器时尤其重要。

什么时候该换掉它

我的判断标准是四条,中一条就换:

信号 说明
需要多台机器同时写 这是硬约束,SQLite 没有方案
长事务频繁出现 一个长写事务会堵住所有写
单表过千万行且有复杂聚合 缺并行查询,大聚合会拖慢全局
需要在写入时做在线扩容 加列容易,改结构仍需重建表

反过来说,只要满足「单机、读多写少、数据量在百万行以内、没有长事务」, SQLite 的性能和运维成本都优于另起一个数据库进程。零配置、零端口、备份就是一个文件,这些都是实打实的好处。

几个容易忘的小事

  • Python 的 sqlite3 默认不开 WAL,要显式执行 PRAGMA,且每个连接都要。
  • 连接不要跨线程复用。默认 check_same_thread=True,要么每线程一个连接,要么明确自己管锁。
  • 批量插入必须用事务包起来。否则每条 insert 都是一次 fsync,慢几十倍:
    with conn:            # 自动开事务、成功提交、异常回滚
        conn.executemany("INSERT INTO t VALUES (?,?)", rows)
  • VACUUM 需要额外磁盘空间,删除大量数据后想做瘦身,先确认剩余空间够一份完整副本。

结论:不是「小项目才用 SQLite」,而是「单写者的场景用 SQLite」。 只要跨过单机这条线,它立刻不合适;没跨过去之前,它几乎总是更省事的选择。