SQLite 生产级部署:目录、权限、性能调优、容灾备份与多机方案
SQLite 不只是学习玩具,用对了单机能扛百万级 QPS。本文整理生产环境部署规范:目录权限、systemd 守护、WAL + page_size + cache 调优、SQLITE_BUSY/锁等待、Litestream 实时备份到对象存储、以及什么时候该换 PostgreSQL 的边界。
SQLite 生产级部署:目录、权限、性能调优、容灾备份与多机方案
SQLite 在入门里是”零配置就能用”,但真要稳定跑在生产机器上当业务数据库(比如博客 CMS、内部后台、独立 SaaS、嵌入式网关),细节还是很多的:目录放哪里、权限怎么锁、WAL 怎么开、文件系统用啥、磁盘 IOPS 够不够、被并发一压就
SQLITE_BUSY怎么处理、数据库坏了怎么救、备份频率怎么定、什么时候必须换成 PostgreSQL…… 本文一次性说清。
一、部署边界:什么时候能用 SQLite,什么时候别硬扛
先把最关键的话放前面——SQLite 是单文件嵌入式数据库,它的天花板来自于”单写”与”单机”:
| 维度 | 生产能用 ✅ | 别硬上 ❌ |
|---|---|---|
| 部署形态 | 单实例、单主机(Web + SQLite 放同一台机器 / 同一个 Pod) | 多台应用服务器共用 1 个 SQLite 文件(网络盘 NFS/SMB 也不行) |
| 写入并发 | 秒杀/抢购等 > 1 万次写/秒的高并发 OLTP | |
| 数据库体积 | 单库 ≤ 1TB(官方建议),实测推荐 ≤ 100GB(维护 VACUUM / 备份时间都友好) | PB 级数据仓库 |
| 读 QPS | 单机轻松上万到十万级(读多写少的博客 / 文档站 / BI 报表首选) | 跨区域多活、读副本横向扩展 |
| 典型业务 | 博客 CMS、内部后台、个人/小团队 SaaS、IoT 边缘网关、嵌入式设备、CI/CD 元数据存储 | 电商交易核心、社交动态、金融记账、分布式微服务共享库 |
超过了怎么办? → 换成 PostgreSQL(单机能 SQLite 10 倍吞吐,还能上一主多从)。SQLite 的优势是运维成本≈0,当你的团队能接受 PostgreSQL 的运维成本时,PostgreSQL 是更通用的选择。
二、单机部署规范
2.1 目录与文件系统选择
| 类别 | 建议 | 原因 |
|---|---|---|
| 数据库放哪 | /var/lib/<app>/data/app.db (FHS 规范) | 与程序、配置、日志分磁盘分区或至少分目录管理 |
| WAL 日志目录 | 同目录(<app>-wal、<app>-shm) | SQLite 默认与 db 文件同目录,必须跟主库在同一个文件系统 |
| 备份放哪 | /var/backups/<app>/ + 对象存储异地副本 | 不能和数据库放在同一台机器同一块盘,盘坏了一起没 |
| 文件系统 | XFS / ext4 任选,必须是本地盘;禁止 NFS / SMB / GlusterFS 等网络共享盘 | 网络共享盘的文件锁语义不完整,轻则写锁卡住重则 db 文件损坏 |
| 存储介质 | SSD 优先,顺序写 IOPS ≥ 500,延迟 < 1ms | SQLite 每次 COMMIT 就是一次随机+顺序写,HDD 遇到并发事务会变成”秒级提交” |
| atime | 挂载 noatime / strictatime=off | 省掉每次读一个 page 就写一次 inode 的小开销 |
/etc/fstab 示例(假设专用盘 /dev/sdb1 挂到 /data):
UUID=xxxx-xxxx-xxxx /data xfs defaults,noatime,nodiratime 0 2
2.2 目录 & 权限:最小化原则
# 1) 建应用专用运行用户(别用 root 跑业务)
sudo useradd --system --home /var/lib/myapp --shell /usr/sbin/nologin --create-home myapp
# 2) 建目录树
sudo mkdir -p /var/lib/myapp/data /var/log/myapp /var/backups/myapp
sudo chown -R myapp:myapp /var/lib/myapp /var/log/myapp /var/backups/myapp
# 3) 数据目录:只有 myapp 用户能读写,禁止其他用户看
sudo chmod 0750 /var/lib/myapp/data
# 4) 备份目录:myapp 能写,不能随便删;运维组能读
sudo chmod 0750 /var/backups/myapp
sudo chown -R myapp:ops /var/backups/myapp
# 5) db 文件本身(后续创建出来后):0640 够用
# -rw-r----- 1 myapp myapp ... app.db
2.3 用 systemd 守护你的应用(SQLite 随应用一起跑)
SQLite 本身不是服务,随你的 App 进程一起启动。把 App 写成 systemd service,顺便加上资源限制、自动重启:
/etc/systemd/system/myapp.service:
[Unit]
Description=My App (uses SQLite)
After=network.target local-fs.target
# 要求 /var/lib/myapp 所在盘挂好才启动
RequiresMountsFor=/var/lib/myapp
[Service]
Type=simple
User=myapp
Group=myapp
WorkingDirectory=/opt/myapp
ExecStart=/opt/myapp/bin/myapp --config /etc/myapp/config.yaml
Restart=on-failure
RestartSec=5s
# 资源限制(保护宿主机,避免 App bug 吃光内存导致 SQLite 写坏)
MemoryMax=2G
CPUQuota=200% # 2 核
LimitNOFILE=65536
# 加固:禁止提权、隐藏 /home、只读挂载除了必要目录
NoNewPrivileges=true
ProtectHome=tmpfs
ProtectSystem=strict
ReadWritePaths=/var/lib/myapp /var/log/myapp /tmp
PrivateTmp=true
# 崩溃时自动生成核心 dump 方便定位
LimitCORE=infinity
[Install]
WantedBy=multi-user.target
启用:
sudo systemctl daemon-reload
sudo systemctl enable --now myapp
systemctl status myapp
2.4 Docker / Kubernetes 里怎么放 SQLite 文件
Docker Compose 单机
services:
myapp:
image: myapp:1.0
user: "1001:1001" # 与宿主机 myapp 的 uid/gid 对齐
volumes:
- type: bind
source: /var/lib/myapp/data
target: /app/data
- type: tmpfs # 临时文件走内存,减少落盘
target: /tmp
read_only: true # 根文件系统只读,只能写上面挂出来的目录
restart: unless-stopped
Kubernetes 部署要点
- 不要用 Deployment(多副本共享一个 PVC 会写冲突),要么
replicas: 1,要么换成 StatefulSet + 每 Pod 独立 Volume。 - 要多副本?—— 直接换 PostgreSQL 吧,SQLite 不擅长。
- Volume 选择 本地盘 / hostPath / LocalPV,别用 NFS 类的共享存储。
securityContext.runAsNonRoot: true+fsGroup对齐权限。
三、连接初始化:每个进程连接数据库必须设置的 PRAGMA
应用层每个 DB connection 打开后,第一个动作就是跑这些 PRAGMA(顺序写在 connection pool 的初始化里,忘记开就是线上事故):
-- 1) 必须开的
PRAGMA foreign_keys = ON; -- 外键约束(默认 OFF,别写了 REFERENCES 但是摆设)
PRAGMA journal_mode = WAL; -- WAL 模式(高并发基础,不建议 DELETE/TRUNCATE 模式)
PRAGMA busy_timeout = 5000; -- 拿不到写锁等 5 秒再返回 SQLITE_BUSY(默认立即返回错误!)
PRAGMA synchronous = NORMAL; -- WAL 模式下 NORMAL 已经能保证断电不损坏;写密集业务再调 FULL
-- 2) 性能优化(按机型调)
PRAGMA page_size = 4096; -- 建议等于磁盘 block size(SSD 4K,Linux stat / 看)
PRAGMA cache_size = -262144; -- 页缓存 = 262144 * 4KB = 1GB(负号代表 KBytes,正号是 pages)
PRAGMA mmap_size = 2147483648; -- 2GB 内存映射,减少 read() 系统调用开销(读多大的 DB 就开多大,别超内存)
PRAGMA temp_store = MEMORY; -- 临时表/索引/排序放内存,别落盘
PRAGMA wal_autocheckpoint = 2000; -- 累积 2000 页(约 8MB)触发一次自动 checkpoint
PRAGMA journal_size_limit = 67108864; -- WAL 文件最大 64MB,异常时防无限变大
不同语言里的写法举例(别漏!):
// Go: modernc.org/sqlite
db, err := sql.Open("sqlite",
"file:/var/lib/myapp/data/app.db?"+
"_pragma=journal_mode(WAL)&"+
"_pragma=busy_timeout(5000)&"+
"_pragma=foreign_keys(1)&"+
"_pragma=synchronous(NORMAL)&"+
"_pragma=cache_size(-262144)&"+
"_pragma=mmap_size(2147483648)")
# Python: sqlite3 / apsw
con = sqlite3.connect("/var/lib/myapp/data/app.db")
con.execute("PRAGMA journal_mode = WAL;")
con.execute("PRAGMA busy_timeout = 5000;")
con.execute("PRAGMA foreign_keys = ON;")
con.execute("PRAGMA synchronous = NORMAL;")
con.execute("PRAGMA cache_size = -262144;")
// Node.js: better-sqlite3
const Database = require('better-sqlite3');
const db = new Database('/var/lib/myapp/data/app.db', { readonly: false });
db.pragma('journal_mode = WAL');
db.pragma('busy_timeout = 5000');
db.pragma('foreign_keys = ON');
db.pragma('synchronous = NORMAL');
db.pragma('cache_size = -262144');
db.pragma('mmap_size = 2147483648');
busy_timeout 这条新手最容易忘——它决定了多线程并发写入时,是立刻返回
SQLITE_BUSY database is locked,还是”自旋+等几秒再重试”。5000ms 是对大多数 Web App 合理的折中。
四、性能调优实战
4.1 读性能:索引 + 查询分析
SQLite 是单表 B-Tree 索引(MySQL InnoDB 是聚簇索引,概念有点像但不完全一样)。用三件套:
-- ① 先"解释"你的慢查询
EXPLAIN QUERY PLAN
SELECT u.*, COUNT(o.id) AS orders
FROM users u
LEFT JOIN orders o ON o.user_id = u.id
WHERE u.status = 1 AND u.created_at > '2026-01-01'
GROUP BY u.id
ORDER BY orders DESC
LIMIT 10;
-- 典型差的输出:SCAN users / SCAN orders(全表扫 × 全表扫 = 笛卡尔积慢死)
-- 目标输出:SEARCH users USING INDEX... + SEARCH orders USING INDEX...
-- ② 建缺少的索引
CREATE INDEX idx_users_status_created ON users(status, created_at);
CREATE INDEX idx_orders_user_id ON orders(user_id);
-- ③ 再 EXPLAIN QUERY PLAN 一次,确认走索引
4.2 索引三原则(别瞎建)
- WHERE / JOIN / ORDER BY 里出现的列才建索引,所有列都建等于没建(INSERT 变慢 + 盘空间暴涨)。
- 联合索引遵循 最左前缀原则:
(status, created_at)能命中WHERE status=?、WHERE status=? AND created_at>?,但不能单独命中WHERE created_at>?。 - 区分度低的列(比如性别 0/1 只有两个值)单独建索引没用,放在联合索引末尾当”精筛条件”可以。
4.3 写性能:短事务 + 合并批量写
SQLite 写慢几乎都是下面三种:
| 症状 | 根因 | 解决 |
|---|---|---|
| 每条 INSERT 独立提交,1 万条写了 30 秒 | 每条 = 一次 fsync(事务提交),磁盘 fsync 有物理上限 | BEGIN; INSERT 1000 条; COMMIT; 批量提交 → 从 30s 降到 ~100ms |
| 事务里还做网络/外部调用 | 事务一直不 COMMIT,占着写锁,其他连接被 BUSY | 先查/先准备数据,事务里只做 DB 操作,别把 HTTP RPC 包进去 |
| 自动 AUTOCHECKPOINT 卡住主线程 | 写入太猛,checkpoint 跟不上 | 后台开独立连接跑 PRAGMA wal_checkpoint(PASSIVE); 周期做;或者 wal_autocheckpoint 调大 |
批量导入 CSV 的正确姿势:
sqlite3 /var/lib/myapp/data/app.db <<'EOF'
PRAGMA journal_mode = OFF; -- 导入时临时关掉日志
PRAGMA synchronous = 0; -- 不刷盘
PRAGMA cache_size = -4000000; -- 4GB 缓存
.mode csv
.import --skip 1 bigdata.csv target_table
CREATE INDEX idx_target ON target_table(col1, col2);
PRAGMA journal_mode = WAL; -- 导完切回 WAL
PRAGMA synchronous = NORMAL;
ANALYZE; -- 更新统计信息,让查询计划准确
VACUUM; -- 重写整个文件,紧凑化
EOF
五、SQLITE_BUSY & 锁等待深入解析
SQLite 有 5 种锁状态(UNLOCKED / SHARED / RESERVED / PENDING / EXCLUSIVE),日常遇到的就两种:
- 读/读不冲突:1000 个
SELECT同时跑完全 OK(都拿 SHARED 锁)。 - 写/读:WAL 模式下写者在做 checkpoint 前只拿
RESERVED,读者可以继续读旧快照页——读写并发大大提升,这就是为什么生产一定要开 WAL。 - 写/写冲突:两个线程同时要 COMMIT,必须一个一个来。第二个写者要么等 busy_timeout(我们前面设的 5000ms),要么超时就返回
SQLITE_BUSY。
如果业务真的写冲突频繁:
- 应用层连接池要大一点(写线程排队在内存里等,别让用户请求直接 BUSY)
- Go 用
database/sql.SetMaxOpenConns(64)+SetMaxIdleConns(32);Python/Node 同理 - 把”非关键但高并发的写”放进单写者协程 + 队列合并批量写(如统计点击量、埋点)
- 实在要多线程高并发写 → 该考虑 PostgreSQL 了
六、备份 & 容灾(这一块最容易把 SQLite 用”死”)
6.1 热备份:.backup 命令定期做
每个 SQLite 文档第一推荐:
# 安全,可在线执行(内部拿一致性快照,不会影响读写)
sqlite3 /var/lib/myapp/data/app.db ".backup /var/backups/myapp/backup-$(date +%Y%m%d-%H%M%S).db"
配合 cron(systemd timer 更规范):
# /etc/systemd/system/myapp-backup.timer
[Unit]
Description=My App SQLite hourly backup
[Timer]
OnCalendar=hourly
Persistent=true
[Install]
WantedBy=timers.target
# /etc/systemd/system/myapp-backup.service
[Unit]
Description=My App SQLite backup
[Service]
Type=oneshot
User=myapp
WorkingDirectory=/var/backups/myapp
ExecStart=/bin/sh -c '\
STAMP=$(date +%%Y%%m%%d-%%H%%M%%S) && \
/usr/bin/sqlite3 /var/lib/myapp/data/app.db ".backup /var/backups/myapp/app-${STAMP}.db" && \
gzip -f /var/backups/myapp/app-${STAMP}.db && \
/usr/bin/find /var/backups/myapp -name "*.db.gz" -mtime +7 -delete'
6.2 进阶:Litestream 实时流式增量备份到 S3/OSS(强烈推荐生产使用)
Litestream(https://litestream.io)是 Ben Johnson 写的开源工具,能把 SQLite 的 WAL 文件逐页实时 streaming 到 S3 / 兼容 S3 的对象存储(阿里云 OSS、MinIO、Cloudflare R2…)。
- RPO = 秒级(机器断电就丢几秒钟写)
- 成本:100GB DB 一个月才几美元(S3 标准存储+很少的 PUT 请求)
- 甚至可以 streaming replica:另起一台备机持续 replay WAL,秒级切过去
安装 & 跑:
# 官方 .deb / .rpm / 二进制
wget https://github.com/benbjohnson/litestream/releases/latest/download/litestream-v0.3.13-linux-amd64.deb
sudo dpkg -i litestream*.deb
/etc/litestream.yml(阿里云 OSS S3 兼容举例):
access-key-id: LTAI5xxxxxxx
secret-access-key: xxxxxxxxxxxxxxxxx
dbs:
- path: /var/lib/myapp/data/app.db
replicas:
- type: s3
bucket: myapp-sqlite-backups
endpoint: https://oss-cn-hangzhou.aliyuncs.com
region: cn-hangzhou
path: prod/app.db
force-path-style: true
retention: 72h # 保留 72 小时内的 WAL,允许回滚到任意时间点
sync-interval: 1s # 每隔 1s flush 一次(默认也是 1s)
systemd 守护起来:
# /etc/systemd/system/litestream.service
[Unit]
Description=Litestream
After=network.target
[Service]
Type=simple
User=myapp
Group=myapp
ExecStart=/usr/bin/litestream replicate -config /etc/litestream.yml
Restart=always
RestartSec=5s
[Install]
WantedBy=multi-user.target
灾难恢复(数据库所在盘彻底挂了):
# 从 S3 把 db 还原到某时刻(不指定 -timestamp 就是最新状态)
litestream restore -config /etc/litestream.yml \
-o /tmp/app-restored.db \
-timestamp "2026-08-26T14:30:00+08:00" \
/var/lib/myapp/data/app.db
用了 Litestream 基本可以宣告”单机 SQLite 的 RPO < 10s,RTO < 5min”,这在小型业务里完全够用。
6.3 RTO 演练(一定要跑一次)
“备份能不能用”不是备份完就算了,一定要在测试机器上还原一次:
# ① 从 S3 / 备份目录拷一份 .db.gz 到测试机
# ② 还原 gz
gunzip -c backup-xxx.db.gz > /tmp/restore-test.db
# ③ 完整性校验(SQLite 自带)
sqlite3 /tmp/restore-test.db "PRAGMA integrity_check;"
# 输出 = ok 就是完整
# ④ 跑一下你 App 最核心的 5 条 SQL,看对不对
sqlite3 /tmp/restore-test.db "SELECT COUNT(*) FROM users;"
sqlite3 /tmp/restore-test.db "SELECT * FROM orders ORDER BY created_at DESC LIMIT 1;"
七、常见生产问题 & 排障
7.1 报错对照表
| 报错关键字 | 根因 | 排查与修复 |
|---|---|---|
SQLITE_BUSY: database is locked | 写锁被占 + 超过 busy_timeout 还没等到 | ① 加 PRAGMA busy_timeout = 5000② 业务上事务做短,别把 RPC 包进去 ③ 看看是不是被备份/ VACUUM 长事务占着锁(lsof app.db 看哪些进程)④ 同一进程里多个 goroutine 没开 WAL 也容易触发 |
SQLITE_READONLY: attempt to write a readonly database | 文件权限 / SELinux / 文件系统只读挂载 | namei -l /var/lib/myapp/data/app.db 看每级目录权限;mount | grep /data 看 ro 还是 rw;ausearch -m avc -ts recent 看是不是 SELinux 拦了 |
SQLITE_CORRUPT: database disk image is malformed | 数据库损坏(异常断电 + synchronous=OFF / fsync 失败 / 磁盘坏道) | 1) 立刻备份一份原样(修复可能更坏) 2) .dump 能救多少救多少:sqlite3 bad.db .dump > out.sql && sqlite3 new.db < out.sql3) .recover 命令(3.30+,比 .dump 对损坏容忍度更高) |
SQLITE_FULL: database or disk is full | 磁盘满了 / quota 超限 | df -h /var/lib/myapp/data + du -sh * 查谁占满;赶紧清 WAL / 旧备份;再不行就加盘或 VACUUM INTO 迁新盘 |
| WAL 文件(-wal)持续几十 GB 不收缩 | 有一个长 SELECT 事务一直不 END,阻碍 checkpoint;或者 wal_autocheckpoint 关了/调太大了 | ① 找出长事务:应用层看连接池 idle in transaction;lsof app.db-wal 看进程② 手动触发一次 PRAGMA wal_checkpoint(TRUNCATE);(会短暂拿写锁,业务低峰做)③ 调小 journal_size_limit 做保险 |
SQLITE_IOERR 系列 | 真的 IO 错误(磁盘坏块、SSD 寿命到、盘被拔出) | `dmesg |
| App 重启后第一次读就慢 | cache 是连接级的,新连接全冷;或 mmap_size 没开 | ① 加大 cache_size 并确保连接池复用② PRAGMA mmap_size 尽量开到 ≥ DB 大小③ 启动后先跑一轮核心 SQL warmup |
7.2 性能退化排查标准流程
# 1. DB 多大 / 多少页
sqlite3 app.db "PRAGMA page_count; PRAGMA page_size; PRAGMA freelist_count;"
# page_count * page_size ≈ 文件逻辑大小
# freelist_count 大 = 大量空页,VACUUM 能回收空间
# 2. 哪张表/哪个索引最大
sqlite3 app.db <<'EOF'
.headers on
.mode column
SELECT
name,
ROUND(pgsize / 1024.0 / 1024.0, 2) AS size_mb,
pageno
FROM dbstat
ORDER BY pgsize DESC
LIMIT 20;
EOF
# 3. 慢查询抓日志(Python/Go/Node 应用层自己记 SQL+耗时,或临时开 sqlite3_trace)
# 4. 最常走的 10 条 SQL 全部跑一遍 EXPLAIN QUERY PLAN,把 SCAN 的全改成 SEARCH INDEX
# 5. 最后跑一次
sqlite3 app.db "ANALYZE;"
# 更新统计信息(SQLite 靠它选索引,大表数据分布变了一定要定期跑)
八、多机部署对比(SQLite 生态里”接近共享”的几种方案)
如果你必须跨机器用”SQLite 语义”的数据库,几条路可以选:
| 方案 | 原理 | 一致性 | 优点 | 缺点 | 适用 |
|---|---|---|---|---|---|
| 单机 + Litestream 冷备 | 单写,流式备份 S3,灾时切到新单机 | 单机强一致 | 最简单,运维最低 | 切机要几分钟 | 99% 小业务 |
| Litestream Streaming Replica | 主实例写 → WAL stream 到 S3;副本实例持续 S3 replay,可只读 | 异步(秒级延迟) | 读可横向扩展 | 写仍只能主 | 读多写少、要读副本 |
| rqlite | 把 SQLite 包一层 Raft 共识集群(3/5 节点),SQL 语句通过 Raft log 同步 | 强一致(CP) | 真·分布式,节点故障自动切主 | 写入性能 ≈ 最慢节点;和原生 SQLite API 不完全兼容(要走 HTTP/gRPC 客户端) | 必须多活、规模不大 |
| dqlite(Canonical 出品) | 类似 rqlite,也是 Raft + SQLite,C 实现 + Go 绑定 | 强一致 | 被 K3s/microk8s 采用,稳定 | 社区比 rqlite 小点 | K8s 边缘集群场景 |
| Turso / libSQL(商业化) | SQLite fork,支持边缘多节点 sqld 服务 + 嵌入式库 | 异步同步,全球边缘读 | 全球 CDN 级读性能;官方有免费额度 | 写要回主区域;成本随规模上涨 | SaaS、Edge App |
| 直接换 PostgreSQL(推荐) | 走成熟关系型数据库 | 成熟 20 年强一致生态 | 最通用;所有 ORM / 驱动原生支持 | 要维护 PG 实例(或用云托管 RDS/Aurora/Cloud SQL 省运维) | 超过单机 SQLite 天花板的时候 |
迁移路线建议:
单机 SQLite → + Litestream → + Streaming Replica → 业务真的要多写了才切 PostgreSQL —— 大部分项目一辈子卡在第一步就够用了。
九、健康巡检清单(每周/每月跑一次)
- 完整性检查:
sqlite3 app.db "PRAGMA integrity_check;"(每次备份后跑一份) - 备份可还原吗:恢复演练(每月至少挑一次随机备份还原)
- 磁盘空闲:
df -h数据盘 > 85% 就要扩容 / 清理 - SQLITE_BUSY 错误率:监控 App 日志里 SQLITE_BUSY 出现次数/占比,趋势异常就查
- WAL 文件大小:
ls -lh app.db-wal,长期 > 1GB 要查 checkpoint - 慢查询:应用 APM / 日志看 P95/P99 SQL 延迟,跑 ANALYZE + 补索引
- OS / SSD 健康:
smartctl看 SMART,dmesg看 IO error - Litestream sync 正常吗:
litestream status -config /etc/litestream.yml,最后一个 WAL 复制时间
参考资料
- SQLite 官方文档(生产必读):
- WAL 模式:https://www.sqlite.org/wal.html
- 文件锁:https://www.sqlite.org/lockingv3.html
- 如何破坏 SQLite:https://www.sqlite.org/howtocorrupt.html (反着读 = 生产最佳实践)
- 运行时限制:https://www.sqlite.org/limits.html
- Litestream:https://litestream.io/
- rqlite:https://rqlite.io/
- dqlite:https://dqlite.io/
- Turso / libSQL:https://turso.tech/
- Ben Johnson 博客:https://www.gobriefcases.com/ (SQLite 在生产大规模使用的实战经验系列)