SQLite 用在单机服务里,什么样的场景合适
我维护过一个单机服务,数据量不到十万行,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」。 只要跨过单机这条线,它立刻不合适;没跨过去之前,它几乎总是更省事的选择。