胖叔网络科技 网络科技 · 技术笔记
数据与存储

SQLite 在 1GB 内存服务器上的调优笔记

单文件数据库在低配机器上其实非常能打,前提是把 WAL、缓存和同步策略这几个参数配对。附完整的 PRAGMA 配置和三个反模式。

# SQLite# WAL# 低配服务器# 性能优化

#为什么在小机器上反而该用 SQLite

选型时最常见的误判是:认为 SQLite 是"玩具数据库",生产必须上 MySQL

在单进程、单机、读多写少的场景下,SQLite 的实测表现反而更好:

  • 零额外内存:不占独立进程,MySQL 空跑也要 300 MB 起
  • 零网络开销:没有 TCP 往返,读操作直接走文件页缓存
  • 备份极简:就是一个文件,cp 或者走 backup API 都行

它真正的限制只有一条:写入是串行的,且不适合多主机共享。个人站点几乎不可能触到这条线。

#关键 PRAGMA 配置

初始化连接时执行这几条,顺序也重要:

JavaScript
const db = new Database('data/blog.db');

db.pragma('journal_mode = WAL');   // 必须第一个执行
db.pragma('synchronous = NORMAL');
db.pragma('busy_timeout = 5000');
db.pragma('cache_size = -20000');  // 负值单位为 KiB,即约 20MB
db.pragma('temp_store = MEMORY');
db.pragma('foreign_keys = ON');

#journal_mode = WAL 到底改了什么

默认的 DELETE 模式下,写事务会锁住整个数据库,读操作必须等待。WAL(Write-Ahead Logging)改成"写日志文件、读快照",读写互不阻塞

代价是会多出两个附属文件:

CODE
data/blog.db
data/blog.db-wal   ← 预写日志
data/blog.db-shm   ← 共享内存索引

这直接影响备份策略:不能只复制 blog.db,因为最新的数据可能还在 -wal 里。正确做法是用 backup API:

JavaScript
db.backup('/path/to/backup.db');  // 在线一致性快照,自动处理 WAL

#synchronous 该选哪个

这是性能与持久性的权衡点,三个选项的实际差别:

取值 写入速度 断电风险 建议
FULL 几乎无 金融类数据
NORMAL 约 3× 可能丢最后几条已提交事务,数据库不会损坏 绝大多数场景
OFF 最快 可能损坏数据库 不要用

WAL 模式下 NORMAL 已经能保证数据库文件本身不会损坏,最多丢最近几条写入。个人博客完全可接受。

#cache_size 的负值是什么意思

这是个容易看错的参数:

  • 正值:单位是"页数",默认 2000 页 × 4 KB = 约 8 MB
  • 负值:单位是 KiB,-20000 = 20000 KiB ≈ 20 MB

小库(几十 MB 以内)设成 -20000 基本可以让热数据全部驻留内存。但别超过 -30000,1 GB 的机器经不起。

#三个必须避开的反模式

#反模式一:多进程共享同一个库文件

WAL 模式依赖共享内存(-shm 文件),这个机制只在同一台主机内有效

CODE
PM2 cluster 模式开 4 个实例,共享一个 SQLite 文件
❌ 两个 Docker 容器挂载同一个 SQLite 文件
❌ SQLite 文件放在 NFS / 网络盘上

正确做法:单进程(PM2 fork 模式)。真需要多实例,说明该换 PostgreSQL 了。

#反模式二:热备份直接 cp

Bash
cp blog.db backup.db          # 可能复制到写入中的半截状态
✅ 用 backup API 或先停服务

如果一定要用 shell 脚本备份,正确姿势是先做 WAL checkpoint:

Bash
sqlite3 data/blog.db "PRAGMA wal_checkpoint(TRUNCATE);"
cp data/blog.db backup/blog-$(date +%F).db

#反模式三:给每个字段建索引

索引会让写入变慢、文件变大。SQLite 的查询优化器对单表小数据量本来就能全表扫得很快。

判断标准很简单:只给出现在 WHEREORDER BYJOIN 里的高频字段建索引。可以用 EXPLAIN QUERY PLAN 验证索引有没有被真正用上:

SQL
EXPLAIN QUERY PLAN
SELECT * FROM posts WHERE status = 'published' ORDER BY published_at DESC;

输出里出现 USING INDEX idx_posts_status 才算生效。如果显示 SCAN posts,说明索引没被用上,白建了。

#相关资源

以下资料可直接下载:

SH

数据自动备份脚本

每日定时备份站点数据库与附件目录,自动保留最近 14 天并输出清理日志,可直接配到宝塔「计划任务」。

auto-backup.sh 1.2 KB 26 次下载

#什么时候该考虑换掉 SQLite

出现下面任一情况,就该认真评估迁移:

  1. 需要多进程/多机写入 —— SQLite 的架构性限制
  2. 单表超过千万行且查询复杂 —— 查询优化器能力有限
  3. 需要真正的并发写入 —— 写锁是库级的
  4. 需要在线扩容/主从 —— 单文件做不到

对个人站点来说,这些基本都不会发生。不要为了想象中的规模提前付出复杂度成本。

配套的调优参数表可以在文末下载,直接改改就能用。

附件下载 共 1 个文件,点击直接下载

JSON

低配服务器 SQLite 调优参数表

1 GB 内存小机器上 SQLite 的推荐 PRAGMA 配置,含每一项的作用说明与副作用提示,可直接复制使用。

sqlite-tuning.json 1.8 KB 48 次下载 来自:SQLite 在 1GB 内存服务器上的调优笔记